classification
Title: inspect not capturing type annotations created by __class_getitem__
Type: Stage:
Components: Library (Lib) Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: gvanrossum, kj, levkivskyi, rhettinger, serhiy.storchaka
Priority: normal Keywords:

Created on 2021-10-11 22:05 by rhettinger, last changed 2021-10-13 11:54 by serhiy.storchaka.

Messages (5)
msg403692 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-10-11 22:05
In the example below, __annotations__ is correct but not the corresponding Signature object.

-----------------------------------------------------------------------

from typing import List

def f(s: List[float]) -> None: pass

def g(s: list[float]) -> None: pass

>>> inspect.signature(f)
<Signature (s: List[float]) -> None>

>>> inspect.signature(g)
<Signature (s: list) -> None>

g.__annotations__
{'s': list[float], 'return': None}
msg403697 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-10-11 22:21
Raymond, the bug must be in the Python code in inspect.py. Could you dig a little deeper there? I don't know much about it. Specifically I think the problem may just be in the repr() of class Parameter:

>>> inspect.signature(g).parameters['s']             
<Parameter "s: list">
>>> inspect.signature(g).parameters['s'].annotation
list[float]
>>>
msg403759 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-10-12 21:40
It looks like the error is in inspect.formatannotation().

For instances of type, that function incorrectly returns only annotation.__qualname__.

Instead, it should return repr(annotation) which would include the args.

=================================================================


def formatannotation(annotation, base_module=None):
    if getattr(annotation, '__module__', None) == 'typing':
        return repr(annotation).replace('typing.', '')
    if isinstance(annotation, type):                # <== Erroneous case
        if annotation.__module__ in ('builtins', base_module):
            return annotation.__qualname__
        return annotation.__module__+'.'+annotation.__qualname__
    return repr(annotation)
msg403760 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-10-12 21:50
Sure. Waiting for your PR. :-)
msg403826 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2021-10-13 11:54
The cause is that isinstance(list[int], type) returns True. It can cause bugs in other parts of the code which test for instance of type. For example:

>>> types.resolve_bases((typing.List[int],))
(<class 'list'>, <class 'typing.Generic'>)
>>> types.resolve_bases((list[int],))
(list[int],)

>>> types.prepare_class('A', (int,), {'metaclass': typing.Type[int]})
(typing.Type[int], {}, {})
>>> types.prepare_class('A', (int,), {'metaclass': type[int]})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/serhiy/py/cpython/Lib/types.py", line 125, in prepare_class
    meta = _calculate_meta(meta, bases)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/serhiy/py/cpython/Lib/types.py", line 139, in _calculate_meta
    if issubclass(base_meta, winner):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: issubclass() argument 2 cannot be a parameterized generic

>>> @functools.singledispatch
... def g(a): pass
... 
>>> @g.register
... def g2(a: typing.List[int]): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/home/serhiy/py/cpython/Lib/functools.py", line 863, in register
    raise TypeError(
    ^^^^^^^^^^^^^^^^
TypeError: Invalid annotation for 'a'. typing.List[int] is not a class.
>>> @g.register(list[int])
... def g2(a): pass
... 
>>> @g.register
... def g3(a: typing.List[int]): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/home/serhiy/py/cpython/Lib/functools.py", line 863, in register
    raise TypeError(
    ^^^^^^^^^^^^^^^^
TypeError: Invalid annotation for 'a'. typing.List[int] is not a class.
>>> @g.register
... def g3(a: list[int]): pass
... 

And many other examples, too many to show here.

Was it mistake to make isinstance(list[int], type) returning True?
History
Date User Action Args
2021-10-13 11:54:59serhiy.storchakasetmessages: + msg403826
2021-10-13 08:51:20serhiy.storchakasetnosy: + serhiy.storchaka
2021-10-12 21:50:11gvanrossumsetmessages: + msg403760
2021-10-12 21:40:49rhettingersetmessages: + msg403759
2021-10-11 22:21:18gvanrossumsetnosy: + kj
messages: + msg403697
2021-10-11 22:05:54rhettingercreate