pythonfunctionpython-typingpython-3.8positional-parameter

SystemError is raised after attempt to create function with positional-only args in runtime inside other one


There is some test code:

some_type = int

def func0():
    def func1(arg: some_type, /):
        pass

func0()

And I get the following error:

Traceback (most recent call last): 
...
SystemError: no locals when loading 'some_type'

However the code below works as expected:

some_type = int

def func0():
    def func1(arg: some_type):
        pass

func0()

And this one is also valid:

some_type = int
exec('''
def func1(arg: some_type, /):
    pass
''')

I know that annotations will no longer be evaluated at definition time in future versions; also it is possible to activate such behaviour in 3.7+ versions. Something like

from __future__ import annotations
some_type = int

def func0():
    def func1(arg: some_type, /):
        pass

func0()

has no problems as well. However, the question is about the current strange behaviour at function definition time. some_type is in no way a local variable of func0, though python thinks so. One more fine version:

def func0():
    some_type = int
    def func1(arg: some_type, /):
        pass

func0()

I've read PEP 570 but not found anything about annotations declarations there.

My python version:

sys.version_info(major=3, minor=8, micro=0, releaselevel='final', serial=0)

Solution

  • This is a bug in cpython -- I've opened an issue for it here: https://bugs.python.org/issue39215

    looking at the disassembly of the two functions, it appears as though it incorrectly uses LOAD_NAME instead of LOAD_GLOBAL when building the annotation type -- here's the diff between one with and without positional only arguments:

    $ diff -u <(python3.9 -m dis t2.py | sed 's/0x[a-f0-9]*/0xdeadbeef/g;s/t2\.py/FILENAME/g') <(python3.9 -m dis t3.py | sed 's/0x[a-f0-9]*/0xdeadbeef/g;s/t3\.py/FILENAME/g')
    --- /dev/fd/63  2020-01-04 16:34:27.372004436 -0800
    +++ /dev/fd/62  2020-01-04 16:34:27.372004436 -0800
    @@ -10,7 +10,7 @@
                  16 RETURN_VALUE
    
     Disassembly of <code object f at 0xdeadbeef, file "FILENAME", line 1>:
    -  2           0 LOAD_NAME                0 (int)
    +  2           0 LOAD_GLOBAL              0 (int)
                   2 LOAD_CONST               1 (('arg',))
                   4 BUILD_CONST_KEY_MAP      1
                   6 LOAD_CONST               2 (<code object g at 0xdeadbeef, file "FILENAME", line 2>)
    

    EDIT: and here's a pull request to fix this (should land in 3.8.2 and 3.9.0a3 if my guess about timing is correct): https://github.com/python/cpython/pull/17826