[issue39694] Incorrect dictionary unpacking when calling str.format

2020-02-20 Thread Akos Kiss


New submission from Akos Kiss :

My understanding was that in function calls, the keys in an **expression had to 
be strings. However, str.format seems to deviate from that and allows 
non-string keys in the mapping (and silently ignores them).

Please, see the transcript below:

>>> def f(): pass
... 
>>> def g(): pass
... 
>>> x = {None: ''}
>>> y = {1: ''}
>>> f(**x)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: f() keywords must be strings
>>> f(**y)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: f() keywords must be strings
>>> g(**x)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: g() keywords must be strings
>>> g(**y)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: g() keywords must be strings
>>> ''.format(**x)
''
>>> ''.format(**y)
''

I could reproduce this (incorrect?) behavior on macOS with python 3.4-3.7 and 
on Ubuntu 18.04 with python 3.6.

--
messages: 362304
nosy: Akos Kiss
priority: normal
severity: normal
status: open
title: Incorrect dictionary unpacking when calling str.format
type: behavior
versions: Python 3.5, Python 3.6, Python 3.7

___
Python tracker 
<https://bugs.python.org/issue39694>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue39694] Incorrect dictionary unpacking when calling str.format

2020-02-20 Thread Akos Kiss


Akos Kiss  added the comment:

Re: Eric

I had a code where `'sometemplate'.format()` got a dictionary from outside (as 
a parameter, not from `locals()`), and that dictionary had `None` as a key. 
Actually, I wasn't even aware of that `None` key until I tried to execute the 
same code in PyPy where it failed with a `TypeError`. That's when I started to 
dig down to the root of the incompatibility.

Now, my code has a workaround to have an `if key is not None` filter in the 
comprehension that constructs the dict. (I'm not sure whether it can be called 
a workaround, since this is how it should have been written in the first place. 
It turns out I was just (ab)using an implementation detail / deviation.)

So much about real-world use cases.

There is one more use case that comes to my mind, but it is admittedly 
theoretical (for now, at least). If I ever wanted to patch/wrap/mock 
`str.format` then the wrapped version would behave differently from the 
built-in version (throw an error when the original does not). But this is 
hypothetical at the moment because I "can't set attributes of 
built-in/extension type 'str'" and don't have the time to experiment with 
complex mocking/patching/wrapping approaches.

--

___
Python tracker 
<https://bugs.python.org/issue39694>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue39694] Incorrect dictionary unpacking when calling str.format

2020-02-20 Thread Akos Kiss


Akos Kiss  added the comment:

I couldn't find any discussion in the language reference about fundamental 
differences between calls to built-in functions and user-defined functions. (I 
tried to read Sections 6.3.4. "Calls" and 8.6. "Function definitions" 
carefully.) Until now I had the impression that built-in and user-defined 
functions should be as similar as possible.

BTW, a user-defined function can also implement support for unused/excess 
keyword arguments (or arguments used on-demand): it only has to add **kwargs at 
the end of its signature. And that's exactly how the documentation specifies 
the signature of format: str.format(*args, **kwargs). That API documentation 
signals (to me at least) that this function should be called just like any 
other function that has a (*args, **kwargs) signature, be it built-in of 
user-defined.

--

___
Python tracker 
<https://bugs.python.org/issue39694>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue39694] Incorrect dictionary unpacking when calling str.format

2020-02-21 Thread Akos Kiss


Akos Kiss  added the comment:

I've come up with some extra examples (use cases?):

```py
import collections

class str2(str):
def format(self, *args, **kwargs):
return super().format(*args, **kwargs)

def unpacknonekey(s):
print('input:', type(s), s)
try:
print('str key:', s.format(**{'bar': 'qux'}))
print('none key:', s.format(**{'bar': 'qux', None: ''}))
except TypeError as e:
print('error:', e)

template = 'foo {bar} baz'
unpacknonekey(template)
unpacknonekey(str2(template))
unpacknonekey(collections.UserString(template))
```

The above script gives the following output:

```
input:  foo {bar} baz
str key: foo qux baz
none key: foo qux baz
input:  foo {bar} baz
str key: foo qux baz
error: format() keywords must be strings
input:  foo {bar} baz
str key: foo qux baz
error: format() keywords must be strings
```

This shows inconsistency between `format` of `str` and subclasses of `str` or 
the standard library's `UserString`.

--

___
Python tracker 
<https://bugs.python.org/issue39694>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue31863] Inconsistent returncode/exitcode for terminated child processes on Windows

2017-10-24 Thread Akos Kiss

New submission from Akos Kiss :

I've been working with various approaches for running and terminating 
subprocesses on Windows and I've obtained surprisingly different results if I 
used different modules and ways of termination. Here is the script I wrote, it 
uses the `subprocess` and the `multiprocessing` modules for starting new 
subprocesses, and process termination is performed either by the modules' own 
`terminate` functions or by `os.kill`.

```py
import multiprocessing
import os
import signal
import subprocess
import sys
import time

def kill_with_os_kill(proc):
print('kill with os.kill(pid,SIGTERM)')
os.kill(proc.pid, signal.SIGTERM)

def kill_with_terminate(proc):
print('kill child with proc.terminate()')
proc.terminate()

def run_and_kill_subprocess(killfn, procarg):
print('run subprocess child with %s' % procarg)
with subprocess.Popen([sys.executable, __file__, procarg]) as proc:
time.sleep(1)
killfn(proc)
proc.wait()
print('child terminated with %s' % proc.returncode)

def run_and_kill_multiprocessing(killfn, procarg):
print('run multiprocessing child with %s' % procarg)
proc = multiprocessing.Process(target=childmain, args=(procarg,))
proc.start()
time.sleep(1)
killfn(proc)
proc.join()
print('child terminated with %s' % proc.exitcode)

def childmain(arg):
print('child process started with %s' % arg)
while True:
pass

if __name__ == '__main__':
if len(sys.argv) < 2:
print('parent process started')
run_and_kill_subprocess(kill_with_os_kill, 'subprocess-oskill')
run_and_kill_subprocess(kill_with_terminate, 'subprocess-terminate')
run_and_kill_multiprocessing(kill_with_os_kill, 
'multiprocessing-oskill')
run_and_kill_multiprocessing(kill_with_terminate, 
'multiprocessing-terminate')
else:
childmain(sys.argv[1])
```

On macOS, everything works as expected (and I think that Linux will behave 
alike):

```
$ python3 killtest.py 
parent process started
run subprocess child with subprocess-oskill
child process started with subprocess-oskill
kill with os.kill(pid,SIGTERM)
child terminated with -15
run subprocess child with subprocess-terminate
child process started with subprocess-terminate
kill child with proc.terminate()
child terminated with -15
run multiprocessing child with multiprocessing-oskill
child process started with multiprocessing-oskill
kill with os.kill(pid,SIGTERM)
child terminated with -15
run multiprocessing child with multiprocessing-terminate
child process started with multiprocessing-terminate
kill child with proc.terminate()
child terminated with -15
```

But on Windows, I got:

```
>py -3 killtest.py
parent process started
run subprocess child with subprocess-oskill
child process started with subprocess-oskill
kill with os.kill(pid,SIGTERM)
child terminated with 15
run subprocess child with subprocess-terminate
child process started with subprocess-terminate
kill child with proc.terminate()
child terminated with 1
run multiprocessing child with multiprocessing-oskill
child process started with multiprocessing-oskill
kill with os.kill(pid,SIGTERM)
child terminated with 15
run multiprocessing child with multiprocessing-terminate
child process started with multiprocessing-terminate
kill child with proc.terminate()
child terminated with -15
```

Notes:
- On Windows with `os.kill(pid, sig)`, "sig will cause the process to be 
unconditionally killed by the TerminateProcess API, and the exit code will be 
set to sig." I.e., it is not possible to detect on Windows whether a process 
was terminated by a signal or it exited properly, because `kill` does not 
actually raise a signal and no Windows API allows to differentiate between 
proper or forced termination.
- The `multiprocessing` module has a workaround for this by terminating the 
process with a designated exit code (`TERMINATE = 0x1`) and checking for 
that value afterwards, rewriting it to `-SIGTERM` if found. The related 
documentation is a bit misleading, as `exitcode` is meant to have "negative 
value -N [which] indicates that the child was terminated by signal N" -- 
however, if the process was indeed killed with `SIGTERM` (and not via 
`terminate`), then `exitcode` will be `SIGTERM` and not `-SIGTERM` (see above). 
(The documentation of `terminate` does not clarify the situation much by 
stating that "on Windows TerminateProcess() is used", since it does not mention 
the special exit code -- and well, it's not even a signal after all, so it's 
not obvious whether negative or positive exit code is to be expected.)
- The `subprocess` module choses the quite arbitrary exit code of 1 and 
documents that "negative value -N indicates that the child was terminated by 
signal N&

[issue31863] Inconsistent returncode/exitcode for terminated child processes on Windows

2017-10-26 Thread Akos Kiss

Akos Kiss  added the comment:

`taskkill /F` sets exit code to 1, indeed. (Confirmed by experiment. Cannot 
find this behaviour documented, though.)

On the other hand, MS Docs state 
(https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal#remarks)
 that termination by a signal "terminates the calling program with exit code 
3". (So, there may be other "valid" exit codes, too.)

--

___
Python tracker 
<https://bugs.python.org/issue31863>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue31863] Inconsistent returncode/exitcode for terminated child processes on Windows

2017-10-26 Thread Akos Kiss

Akos Kiss  added the comment:

A follow-up: in addition to `taskkill`, I've taken a look at another "official" 
way for killing processes, the `Stop-Process` PowerShell cmdlet 
(https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/stop-process?view=powershell-5.1).
 Yet again, documentation is scarce on what the exit code of the terminated 
process will be. But PowerShell and .NET code base is open sourced, so I've dug 
a bit deeper and found that `Stop-Process` is based on 
`System.Diagnostics.Process.Kill()` 
(https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs#L1240),
 while `Process.Kill()` uses the `TerminateProcess` Win32 API 
(https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs#L93).
 Interestingly, `TerminateProcess` is called with -1 (this was surprising, to 
me at least, as exit code is unsigned on Windows AFAIK).

Therefore, I've added two new "kill" implementations to my original code 
experiment (wont repeat the whole code here, just the additions):

```py
def kill_with_taskkill(proc):
print('kill child with taskkill /F')
subprocess.run(['taskkill', '/F', '/pid', '%s' % proc.pid], check=True)

def kill_with_stopprocess(proc):
print('kill child with powershell stop-process')
subprocess.run(['powershell', 'stop-process', '%s' % proc.pid], check=True)
```

And I got:

```
run subprocess child with subprocess-taskkill
child process started with subprocess-taskkill
kill child with taskkill /F
SUCCESS: The process with PID 4024 has been terminated.
child terminated with 1
run subprocess child with subprocess-stopprocess
child process started with subprocess-stopprocess
kill child with powershell stop-process
child terminated with 4294967295

run multiprocessing child with multiprocessing-taskkill
child process started with multiprocessing-taskkill
kill child with taskkill /F
SUCCESS: The process with PID 5988 has been terminated.
child terminated with 1
run multiprocessing child with multiprocessing-stopprocess
child process started with multiprocessing-stopprocess
kill child with powershell stop-process
child terminated with 4294967295
```

My takeaways from the above are that
1) Windows is not consistent across itself,
2) 1 is not the only "valid" "terminated forcibly" exit code, and
3) negative exit code does not work, even if MS itself tries to use it.

BTW, I really think that killing a process with a code of 1 is questionable, as 
quite some apps return 1 themselves just to signal error (but proper 
termination). This makes it hard to tell applications' own error signaling and 
forced kills apart. But that's a personal opinion.

--

___
Python tracker 
<https://bugs.python.org/issue31863>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue31863] Inconsistent returncode/exitcode for terminated child processes on Windows

2017-10-26 Thread Akos Kiss

Akos Kiss  added the comment:

And I thought that my analysis was thorough... Exit code 1 is the way to go, I 
agree now.

--

___
Python tracker 
<https://bugs.python.org/issue31863>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com