Skip to content

__future__.annotations breaks inspect.signature() #87521

Description

@1ace
mannequin
BPO 43355
Nosy @terryjreedy, @ericvsmith, @The-Compiler, @JelleZijlstra, @1ace, @Fidget-Spinner

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2021-03-01.16:02:00.266>
labels = ['3.8', 'type-bug', 'library', '3.9']
title = '__future__.annotations breaks inspect.signature()'
updated_at = <Date 2021-05-04.15:41:06.799>
user = 'https://github.com/1ace'

bugs.python.org fields:

activity = <Date 2021-05-04.15:41:06.799>
actor = 'JelleZijlstra'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2021-03-01.16:02:00.266>
creator = '1ace'
dependencies = []
files = []
hgrepos = []
issue_num = 43355
keywords = []
message_count = 9.0
messages = ['387875', '387937', '388018', '388023', '388025', '388029', '388033', '388169', '392912']
nosy_count = 6.0
nosy_names = ['terry.reedy', 'eric.smith', 'The Compiler', 'JelleZijlstra', '1ace', 'kj']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue43355'
versions = ['Python 3.8', 'Python 3.9']

Activity

  1. 1ace commented on Mar 1, 2021

    1acemannequin
    MannequinAuthor

    We have a pytest that boils down to the following:

    #from __future__ import annotations
    from inspect import Parameter, signature
    
    
    def foo(x: str) -> str:
        return x + x
    
    
    def test_foo():
        expected = (
            Parameter("x", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
        )
    
        actual = tuple(signature(foo).parameters.values())
    
        assert expected == actual
    

    (execute with pip install pytest && pytest -vv test_foo.py)

    I tried importing 3.10 annotations (so that we can get rid of quotes around the class containing foo(), which is omitted here because it isn't necessary to reproduce the bug), but doing so changes the output of inspect.signature() but not the output inspect.Parameter(), causing a mismatch between the two that breaks the test.

    The above passes on 3.7.9, 3.8.7 & 3.9.1, and if I uncomment the first line, it fails on those same versions.
    As can be expected, the annotations import is a no-op on 3.10.0a5 and the test passes either way.

    I expect inspect might have not been correctly updated to support postponed annotations, but I haven't looked at the implementation (I'm not familiar with the CPython codebase at all) so it's just a guess.

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    type-bugAn unexpected behavior, bug, or error
    on Mar 1, 2021
  3. ericvsmith commented on Mar 2, 2021

    @ericvsmith
    Member

    Can you show the values of “expected” and “actual” for both the failures and successes?

  4. 1ace commented on Mar 3, 2021

    1acemannequin
    MannequinAuthor

    Sure thing.

    Putting the reproducing code in test_foo.py and running docker run --rm -it -v $PWD:/code python sh -c 'pip install pytest && pytest -vvv /code/test_foo.py' yields:

    Collecting pytest
      Downloading pytest-6.2.2-py3-none-any.whl (280 kB)
         |████████████████████████████████| 280 kB 516 kB/s
    Collecting toml
      Downloading toml-0.10.2-py2.py3-none-any.whl (16 kB)
    Collecting py>=1.8.2
      Downloading py-1.10.0-py2.py3-none-any.whl (97 kB)
         |████████████████████████████████| 97 kB 1.6 MB/s
    Collecting attrs>=19.2.0
      Downloading attrs-20.3.0-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 1.8 MB/s
    Collecting iniconfig
      Downloading iniconfig-1.1.1-py2.py3-none-any.whl (5.0 kB)
    Collecting packaging
      Downloading packaging-20.9-py2.py3-none-any.whl (40 kB)
         |████████████████████████████████| 40 kB 833 kB/s
    Collecting pluggy<1.0.0a1,>=0.12
      Downloading pluggy-0.13.1-py2.py3-none-any.whl (18 kB)
    Collecting pyparsing>=2.0.2
      Downloading pyparsing-2.4.7-py2.py3-none-any.whl (67 kB)
         |████████████████████████████████| 67 kB 1.5 MB/s
    Installing collected packages: pyparsing, toml, py, pluggy, packaging, iniconfig, attrs, pytest
    Successfully installed attrs-20.3.0 iniconfig-1.1.1 packaging-20.9 pluggy-0.13.1 py-1.10.0 pyparsing-2.4.7 pytest-6.2.2 toml-0.10.2
    ============================= test session starts ==============================
    platform linux -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /usr/local/bin/python
    cachedir: .pytest_cache
    rootdir: /code
    collected 1 item
    
    code/test_foo.py::test_foo FAILED                                        [100%]
    
    =================================== FAILURES ===================================
    ___________________________________ test_foo ___________________________________
    
        def test_foo():
            expected = (
                Parameter("x", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
            )
    
            actual = tuple(signature(foo).parameters.values())
    
    >       assert expected == actual
    E       assert (<Parameter "x: str">,) == (<Parameter "x: 'str'">,)
    E         At index 0 diff: <Parameter "x: str"> != <Parameter "x: 'str'">
    E         Full diff:
    E         - (<Parameter "x: 'str'">,)
    E         ?                 -   -
    E         + (<Parameter "x: str">,)
    
    code/test_foo.py:16: AssertionError
    =========================== short test summary info ============================
    FAILED code/test_foo.py::test_foo - assert (<Parameter "x: str">,) == (<Param...
    ============================== 1 failed in 0.02s ===============================
    
  5. Fidget-Spinner commented on Mar 3, 2021

    @Fidget-Spinner
    Member

    @eric.smith, here's a summarized version. I hope it helps:

    def foo(x: str) -> str: ...

    In Python 3.7 - 3.9 with from __future__ import annotations, inspect.signature sees foo's parameter as:

    <Parameter "x: 'str'">

    Without the future import (and also in Python 3.10):

    <Parameter "x: str">

    The reason for the difference in 3.10 (IIRC) is that inspect.signature auto converts all strings to typing.ForwardRef internally and then resolves them. This is a result of PEP-563 becoming default (also mentioned here https://docs.python.org/3.10/whatsnew/3.10.html#pep-563-postponed-evaluation-of-annotations-becomes-default )

    Prior to 3.10, the future import treats function annotations as strings and inspect.signature _doesn't_ convert them. I don't know of this is a bug or intentional. Especially considering what PEP-563 has to say: https://www.python.org/dev/peps/pep-0563/#resolving-type-hints-at-runtime

    @1ace, a possible workaround if you want full compatibility regardless of the future import is to use typing.get_type_hints:

    (The result doesn't change even with from __future__ import annotations. This also works from 3.7-3.10)
    >>> from typing import get_type_hints
    >>> get_type_hints(foo)
    {'x': <class 'str'>, 'return': <class 'str'>}
  6. 1ace commented on Mar 3, 2021

    1acemannequin
    MannequinAuthor

    Sorry, I just realized you asked for the "success" value as well.

    For python 3.7-3.9 without the import annotations, and for python 3.10, the type annotation is a class, ie. <Parameter "x: str">.

  7. 1ace commented on Mar 3, 2021

    1acemannequin
    MannequinAuthor

    (... and I had the window open for too long and didn't notice Ken's reply before mine ^^')

    @kj: thanks for the workaround! it's good to be able to change our code and not have to depend on a yet-to-be-released patch on each python version :)

    That said, typing.get_type_hints() only returns the type hint, whereas our current test checks for other things (like the default value) which need to match (I think) for cython's use of __signature__? (I inherited all this code, so I'm not familiar with it and its history, and I'm beginning to question this test's necessity.)

  8. ericvsmith commented on Mar 3, 2021

    @ericvsmith
    Member

    I think this is all about: should inspect.signature() resolve string annotations into actual types (via get_type_hints, or whatever)? I don't use inspect much, so I can't offer an opinion there.

  9. terryjreedy commented on Mar 5, 2021

    @terryjreedy
    Member

    3.7 only gets security fixes

  10. JelleZijlstra commented on May 4, 2021

    @JelleZijlstra
    Member

    Python 3.10 will add an eval_str= argument to inspect.signature() that lets you evaluate string annotations. I don't think it makes sense to change anything in the bugfix branches, so I propose that this issue be closed.

  11. transferred this issue fromon Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.8 (EOL)end of life3.9 (EOL)end of lifestdlibStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions