[Python-checkins] bpo-44524: Fix an issue wherein `_GenericAlias._name` was not properly set for specialforms (GH-27614)

ambv webhook-mailer at python.org
Fri Aug 6 09:36:40 EDT 2021


https://github.com/python/cpython/commit/8bdf12e99a3dc7ada5f85bba79c2a9eb9931f5b0
commit: 8bdf12e99a3dc7ada5f85bba79c2a9eb9931f5b0
branch: main
author: Bas van Beek <43369155+BvB93 at users.noreply.github.com>
committer: ambv <lukasz at langa.pl>
date: 2021-08-06T15:36:35+02:00
summary:

bpo-44524: Fix an issue wherein `_GenericAlias._name` was not properly set for specialforms (GH-27614)

Co-authored-by: Ken Jin <28750310+Fidget-Spinner at users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz at langa.pl>

files:
A Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 439d963af5bd4..06141f8bbcb61 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -4791,66 +4791,185 @@ def test_no_isinstance(self):
             issubclass(int, TypeGuard)
 
 
+SpecialAttrsP = typing.ParamSpec('SpecialAttrsP')
+SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
+
+
 class SpecialAttrsTests(BaseTestCase):
+
     def test_special_attrs(self):
-        cls_to_check = (
+        cls_to_check = {
             # ABC classes
-            typing.AbstractSet,
-            typing.AsyncContextManager,
-            typing.AsyncGenerator,
-            typing.AsyncIterable,
-            typing.AsyncIterator,
-            typing.Awaitable,
-            typing.ByteString,
-            typing.Callable,
-            typing.ChainMap,
-            typing.Collection,
-            typing.Container,
-            typing.ContextManager,
-            typing.Coroutine,
-            typing.Counter,
-            typing.DefaultDict,
-            typing.Deque,
-            typing.Dict,
-            typing.FrozenSet,
-            typing.Generator,
-            typing.Hashable,
-            typing.ItemsView,
-            typing.Iterable,
-            typing.Iterator,
-            typing.KeysView,
-            typing.List,
-            typing.Mapping,
-            typing.MappingView,
-            typing.MutableMapping,
-            typing.MutableSequence,
-            typing.MutableSet,
-            typing.OrderedDict,
-            typing.Reversible,
-            typing.Sequence,
-            typing.Set,
-            typing.Sized,
-            typing.Tuple,
-            typing.Type,
-            typing.ValuesView,
+            typing.AbstractSet: 'AbstractSet',
+            typing.AsyncContextManager: 'AsyncContextManager',
+            typing.AsyncGenerator: 'AsyncGenerator',
+            typing.AsyncIterable: 'AsyncIterable',
+            typing.AsyncIterator: 'AsyncIterator',
+            typing.Awaitable: 'Awaitable',
+            typing.ByteString: 'ByteString',
+            typing.Callable: 'Callable',
+            typing.ChainMap: 'ChainMap',
+            typing.Collection: 'Collection',
+            typing.Container: 'Container',
+            typing.ContextManager: 'ContextManager',
+            typing.Coroutine: 'Coroutine',
+            typing.Counter: 'Counter',
+            typing.DefaultDict: 'DefaultDict',
+            typing.Deque: 'Deque',
+            typing.Dict: 'Dict',
+            typing.FrozenSet: 'FrozenSet',
+            typing.Generator: 'Generator',
+            typing.Hashable: 'Hashable',
+            typing.ItemsView: 'ItemsView',
+            typing.Iterable: 'Iterable',
+            typing.Iterator: 'Iterator',
+            typing.KeysView: 'KeysView',
+            typing.List: 'List',
+            typing.Mapping: 'Mapping',
+            typing.MappingView: 'MappingView',
+            typing.MutableMapping: 'MutableMapping',
+            typing.MutableSequence: 'MutableSequence',
+            typing.MutableSet: 'MutableSet',
+            typing.OrderedDict: 'OrderedDict',
+            typing.Reversible: 'Reversible',
+            typing.Sequence: 'Sequence',
+            typing.Set: 'Set',
+            typing.Sized: 'Sized',
+            typing.Tuple: 'Tuple',
+            typing.Type: 'Type',
+            typing.ValuesView: 'ValuesView',
+            # Subscribed ABC classes
+            typing.AbstractSet[Any]: 'AbstractSet',
+            typing.AsyncContextManager[Any]: 'AsyncContextManager',
+            typing.AsyncGenerator[Any, Any]: 'AsyncGenerator',
+            typing.AsyncIterable[Any]: 'AsyncIterable',
+            typing.AsyncIterator[Any]: 'AsyncIterator',
+            typing.Awaitable[Any]: 'Awaitable',
+            typing.Callable[[], Any]: 'Callable',
+            typing.Callable[..., Any]: 'Callable',
+            typing.ChainMap[Any, Any]: 'ChainMap',
+            typing.Collection[Any]: 'Collection',
+            typing.Container[Any]: 'Container',
+            typing.ContextManager[Any]: 'ContextManager',
+            typing.Coroutine[Any, Any, Any]: 'Coroutine',
+            typing.Counter[Any]: 'Counter',
+            typing.DefaultDict[Any, Any]: 'DefaultDict',
+            typing.Deque[Any]: 'Deque',
+            typing.Dict[Any, Any]: 'Dict',
+            typing.FrozenSet[Any]: 'FrozenSet',
+            typing.Generator[Any, Any, Any]: 'Generator',
+            typing.ItemsView[Any, Any]: 'ItemsView',
+            typing.Iterable[Any]: 'Iterable',
+            typing.Iterator[Any]: 'Iterator',
+            typing.KeysView[Any]: 'KeysView',
+            typing.List[Any]: 'List',
+            typing.Mapping[Any, Any]: 'Mapping',
+            typing.MappingView[Any]: 'MappingView',
+            typing.MutableMapping[Any, Any]: 'MutableMapping',
+            typing.MutableSequence[Any]: 'MutableSequence',
+            typing.MutableSet[Any]: 'MutableSet',
+            typing.OrderedDict[Any, Any]: 'OrderedDict',
+            typing.Reversible[Any]: 'Reversible',
+            typing.Sequence[Any]: 'Sequence',
+            typing.Set[Any]: 'Set',
+            typing.Tuple[Any]: 'Tuple',
+            typing.Tuple[Any, ...]: 'Tuple',
+            typing.Type[Any]: 'Type',
+            typing.ValuesView[Any]: 'ValuesView',
             # Special Forms
-            typing.Any,
-            typing.NoReturn,
-            typing.ClassVar,
-            typing.Final,
-            typing.Union,
-            typing.Optional,
-            typing.Literal,
-            typing.TypeAlias,
-            typing.Concatenate,
-            typing.TypeGuard,
-        )
+            typing.Annotated: 'Annotated',
+            typing.Any: 'Any',
+            typing.ClassVar: 'ClassVar',
+            typing.Concatenate: 'Concatenate',
+            typing.Final: 'Final',
+            typing.ForwardRef: 'ForwardRef',
+            typing.Literal: 'Literal',
+            typing.NewType: 'NewType',
+            typing.NoReturn: 'NoReturn',
+            typing.Optional: 'Optional',
+            typing.TypeAlias: 'TypeAlias',
+            typing.TypeGuard: 'TypeGuard',
+            typing.TypeVar: 'TypeVar',
+            typing.Union: 'Union',
+            # Subscribed special forms
+            typing.Annotated[Any, "Annotation"]: 'Annotated',
+            typing.ClassVar[Any]: 'ClassVar',
+            typing.Concatenate[Any, SpecialAttrsP]: 'Concatenate',
+            typing.Final[Any]: 'Final',
+            typing.Literal[Any]: 'Literal',
+            typing.Optional[Any]: 'Optional',
+            typing.TypeGuard[Any]: 'TypeGuard',
+            typing.Union[Any]: 'Any',
+            typing.Union[int, float]: 'Union',
+            # Incompatible special forms (tested in test_special_attrs2)
+            # - typing.ForwardRef('set[Any]')
+            # - typing.NewType('TypeName', Any)
+            # - typing.ParamSpec('SpecialAttrsP')
+            # - typing.TypeVar('T')
+        }
 
-        for cls in cls_to_check:
+        for cls, name in cls_to_check.items():
             with self.subTest(cls=cls):
-                self.assertEqual(cls.__name__, cls._name)
-                self.assertEqual(cls.__qualname__, cls._name)
-                self.assertEqual(cls.__module__, 'typing')
+                self.assertEqual(cls.__name__, name, str(cls))
+                self.assertEqual(cls.__qualname__, name, str(cls))
+                self.assertEqual(cls.__module__, 'typing', str(cls))
+                self.assertEqual(getattr(cls, '_name', name), name, str(cls))
+                for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+                    s = pickle.dumps(cls, proto)
+                    loaded = pickle.loads(s)
+                    self.assertIs(cls, loaded)
+
+    TypeName = typing.NewType('SpecialAttrsTests.TypeName', Any)
+
+    def test_special_attrs2(self):
+        # Forward refs provide a different introspection API. __name__ and
+        # __qualname__ make little sense for forward refs as they can store
+        # complex typing expressions.
+        fr = typing.ForwardRef('set[Any]')
+        self.assertFalse(hasattr(fr, '__name__'))
+        self.assertFalse(hasattr(fr, '__qualname__'))
+        self.assertEqual(fr.__module__, 'typing')
+        # Forward refs are currently unpicklable.
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.assertRaises(TypeError) as exc:
+                pickle.dumps(fr, proto)
+
+        self.assertEqual(SpecialAttrsTests.TypeName.__name__, 'TypeName')
+        self.assertEqual(
+            SpecialAttrsTests.TypeName.__qualname__,
+            'SpecialAttrsTests.TypeName',
+        )
+        self.assertEqual(
+            SpecialAttrsTests.TypeName.__module__,
+            'test.test_typing',
+        )
+        # NewTypes are picklable assuming correct qualname information.
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            s = pickle.dumps(SpecialAttrsTests.TypeName, proto)
+            loaded = pickle.loads(s)
+            self.assertIs(SpecialAttrsTests.TypeName, loaded)
+
+        # Type variables don't support non-global instantiation per PEP 484
+        # restriction that "The argument to TypeVar() must be a string equal
+        # to the variable name to which it is assigned".  Thus, providing
+        # __qualname__ is unnecessary.
+        self.assertEqual(SpecialAttrsT.__name__, 'SpecialAttrsT')
+        self.assertFalse(hasattr(SpecialAttrsT, '__qualname__'))
+        self.assertEqual(SpecialAttrsT.__module__, 'test.test_typing')
+        # Module-level type variables are picklable.
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            s = pickle.dumps(SpecialAttrsT, proto)
+            loaded = pickle.loads(s)
+            self.assertIs(SpecialAttrsT, loaded)
+
+        self.assertEqual(SpecialAttrsP.__name__, 'SpecialAttrsP')
+        self.assertFalse(hasattr(SpecialAttrsP, '__qualname__'))
+        self.assertEqual(SpecialAttrsP.__module__, 'test.test_typing')
+        # Module-level ParamSpecs are picklable.
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            s = pickle.dumps(SpecialAttrsP, proto)
+            loaded = pickle.loads(s)
+            self.assertIs(SpecialAttrsP, loaded)
 
 class AllTests(BaseTestCase):
     """Tests for __all__."""
diff --git a/Lib/typing.py b/Lib/typing.py
index 9c595ae541aa1..51468aeaf5b75 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -461,7 +461,7 @@ class Starship:
     be used with isinstance() or issubclass().
     """
     item = _type_check(parameters, f'{self} accepts only single type.')
-    return _GenericAlias(self, (item,))
+    return _GenericAlias(self, (item,), name="ClassVar")
 
 @_SpecialForm
 def Final(self, parameters):
@@ -482,7 +482,7 @@ class FastConnector(Connection):
     There is no runtime checking of these properties.
     """
     item = _type_check(parameters, f'{self} accepts only single type.')
-    return _GenericAlias(self, (item,))
+    return _GenericAlias(self, (item,), name="Final")
 
 @_SpecialForm
 def Union(self, parameters):
@@ -520,7 +520,12 @@ def Union(self, parameters):
     parameters = _remove_dups_flatten(parameters)
     if len(parameters) == 1:
         return parameters[0]
-    return _UnionGenericAlias(self, parameters)
+
+    if len(parameters) == 2 and type(None) in parameters:
+        name = "Optional"
+    else:
+        name = "Union"
+    return _UnionGenericAlias(self, parameters, name=name)
 
 @_SpecialForm
 def Optional(self, parameters):
@@ -565,7 +570,7 @@ def open_helper(file: str, mode: MODE) -> str:
     except TypeError:  # unhashable parameters
         pass
 
-    return _LiteralGenericAlias(self, parameters)
+    return _LiteralGenericAlias(self, parameters, name="Literal")
 
 
 @_SpecialForm
@@ -604,7 +609,7 @@ def Concatenate(self, parameters):
                         "ParamSpec variable.")
     msg = "Concatenate[arg, ...]: each arg must be a type."
     parameters = tuple(_type_check(p, msg) for p in parameters)
-    return _ConcatenateGenericAlias(self, parameters)
+    return _ConcatenateGenericAlias(self, parameters, name="Concatenate")
 
 
 @_SpecialForm
@@ -652,7 +657,7 @@ def is_str(val: Union[str, float]):
     PEP 647 (User-Defined Type Guards).
     """
     item = _type_check(parameters, f'{self} accepts only single type.')
-    return _GenericAlias(self, (item,))
+    return _GenericAlias(self, (item,), name="TypeGuard")
 
 
 class ForwardRef(_Final, _root=True):
@@ -1237,6 +1242,10 @@ def __subclasscheck__(self, cls):
             if issubclass(cls, arg):
                 return True
 
+    def __reduce__(self):
+        func, (origin, args) = super().__reduce__()
+        return func, (Union, args)
+
 
 def _value_and_type_iter(parameters):
     return ((p, type(p)) for p in parameters)
@@ -1566,7 +1575,7 @@ def __init__(self, origin, metadata):
         if isinstance(origin, _AnnotatedAlias):
             metadata = origin.__metadata__ + metadata
             origin = origin.__origin__
-        super().__init__(origin, origin)
+        super().__init__(origin, origin, name="Annotated")
         self.__metadata__ = metadata
 
     def copy_with(self, params):
diff --git a/Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst b/Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst
new file mode 100644
index 0000000000000..0c9e82050883d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-08-05-18-20-17.bpo-44524.9T1tfe.rst
@@ -0,0 +1,2 @@
+Fixed an issue wherein the ``__name__`` and ``__qualname__`` attributes of
+subscribed specialforms could be ``None``.



More information about the Python-checkins mailing list