[Python-checkins] bpo-40336: Refactor typing._SpecialForm (GH-19620)

Serhiy Storchaka webhook-mailer at python.org
Thu Apr 23 14:26:55 EDT 2020


https://github.com/python/cpython/commit/40ded947f82683f0ed08144599a83d3a1ce719eb
commit: 40ded947f82683f0ed08144599a83d3a1ce719eb
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-04-23T21:26:48+03:00
summary:

bpo-40336: Refactor typing._SpecialForm (GH-19620)

files:
M Lib/typing.py

diff --git a/Lib/typing.py b/Lib/typing.py
index 9383fb8ff3a23..0dcf291950f7d 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -141,8 +141,9 @@ def _type_check(arg, msg, is_argument=True):
     if (isinstance(arg, _GenericAlias) and
             arg.__origin__ in invalid_generic_forms):
         raise TypeError(f"{arg} is not valid as type argument")
-    if (isinstance(arg, _SpecialForm) and arg not in (Any, NoReturn) or
-            arg in (Generic, Protocol)):
+    if arg in (Any, NoReturn):
+        return arg
+    if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol):
         raise TypeError(f"Plain {arg} is not valid as type argument")
     if isinstance(arg, (type, TypeVar, ForwardRef)):
         return arg
@@ -299,41 +300,18 @@ def __deepcopy__(self, memo):
         return self
 
 
-class _SpecialForm(_Final, _Immutable, _root=True):
-    """Internal indicator of special typing constructs.
-    See _doc instance attribute for specific docs.
-    """
-
-    __slots__ = ('_name', '_doc')
-
-    def __new__(cls, *args, **kwds):
-        """Constructor.
-
-        This only exists to give a better error message in case
-        someone tries to subclass a special typing object (not a good idea).
-        """
-        if (len(args) == 3 and
-                isinstance(args[0], str) and
-                isinstance(args[1], tuple)):
-            # Close enough.
-            raise TypeError(f"Cannot subclass {cls!r}")
-        return super().__new__(cls)
-
-    def __init__(self, name, doc):
-        self._name = name
-        self._doc = doc
-
-    @property
-    def __doc__(self):
-        return self._doc
+# Internal indicator of special typing constructs.
+# See __doc__ instance attribute for specific docs.
+class _SpecialForm(_Final, _root=True):
+    __slots__ = ('_name', '__doc__', '_getitem')
 
-    def __eq__(self, other):
-        if not isinstance(other, _SpecialForm):
-            return NotImplemented
-        return self._name == other._name
+    def __init__(self, getitem):
+        self._getitem = getitem
+        self._name = getitem.__name__
+        self.__doc__ = getitem.__doc__
 
-    def __hash__(self):
-        return hash((self._name,))
+    def __mro_entries__(self, bases):
+        raise TypeError(f"Cannot subclass {self!r}")
 
     def __repr__(self):
         return 'typing.' + self._name
@@ -352,31 +330,10 @@ def __subclasscheck__(self, cls):
 
     @_tp_cache
     def __getitem__(self, parameters):
-        if self._name in ('ClassVar', 'Final'):
-            item = _type_check(parameters, f'{self._name} accepts only single type.')
-            return _GenericAlias(self, (item,))
-        if self._name == 'Union':
-            if parameters == ():
-                raise TypeError("Cannot take a Union of no types.")
-            if not isinstance(parameters, tuple):
-                parameters = (parameters,)
-            msg = "Union[arg, ...]: each arg must be a type."
-            parameters = tuple(_type_check(p, msg) for p in parameters)
-            parameters = _remove_dups_flatten(parameters)
-            if len(parameters) == 1:
-                return parameters[0]
-            return _GenericAlias(self, parameters)
-        if self._name == 'Optional':
-            arg = _type_check(parameters, "Optional[t] requires a single type.")
-            return Union[arg, type(None)]
-        if self._name == 'Literal':
-            # There is no '_type_check' call because arguments to Literal[...] are
-            # values, not types.
-            return _GenericAlias(self, parameters)
-        raise TypeError(f"{self} is not subscriptable")
-
-
-Any = _SpecialForm('Any', doc=
+        return self._getitem(self, parameters)
+
+ at _SpecialForm
+def Any(self, parameters):
     """Special type indicating an unconstrained type.
 
     - Any is compatible with every type.
@@ -386,9 +343,11 @@ def __getitem__(self, parameters):
     Note that all the above statements are true from the point of view of
     static type checkers. At runtime, Any should not be used with instance
     or class checks.
-    """)
+    """
+    raise TypeError(f"{self} is not subscriptable")
 
-NoReturn = _SpecialForm('NoReturn', doc=
+ at _SpecialForm
+def NoReturn(self, parameters):
     """Special type indicating functions that never return.
     Example::
 
@@ -399,9 +358,11 @@ def stop() -> NoReturn:
 
     This type is invalid in other positions, e.g., ``List[NoReturn]``
     will fail in static type checkers.
-    """)
+    """
+    raise TypeError(f"{self} is not subscriptable")
 
-ClassVar = _SpecialForm('ClassVar', doc=
+ at _SpecialForm
+def ClassVar(self, parameters):
     """Special type construct to mark class variables.
 
     An annotation wrapped in ClassVar indicates that a given
@@ -416,9 +377,12 @@ class Starship:
 
     Note that ClassVar is not a class itself, and should not
     be used with isinstance() or issubclass().
-    """)
+    """
+    item = _type_check(parameters, f'{self} accepts only single type.')
+    return _GenericAlias(self, (item,))
 
-Final = _SpecialForm('Final', doc=
+ at _SpecialForm
+def Final(self, parameters):
     """Special typing construct to indicate final names to type checkers.
 
     A final name cannot be re-assigned or overridden in a subclass.
@@ -434,9 +398,12 @@ class FastConnector(Connection):
           TIMEOUT = 1  # Error reported by type checker
 
     There is no runtime checking of these properties.
-    """)
+    """
+    item = _type_check(parameters, f'{self} accepts only single type.')
+    return _GenericAlias(self, (item,))
 
-Union = _SpecialForm('Union', doc=
+ at _SpecialForm
+def Union(self, parameters):
     """Union type; Union[X, Y] means either X or Y.
 
     To define a union, use e.g. Union[int, str].  Details:
@@ -461,15 +428,29 @@ class FastConnector(Connection):
 
     - You cannot subclass or instantiate a union.
     - You can use Optional[X] as a shorthand for Union[X, None].
-    """)
-
-Optional = _SpecialForm('Optional', doc=
+    """
+    if parameters == ():
+        raise TypeError("Cannot take a Union of no types.")
+    if not isinstance(parameters, tuple):
+        parameters = (parameters,)
+    msg = "Union[arg, ...]: each arg must be a type."
+    parameters = tuple(_type_check(p, msg) for p in parameters)
+    parameters = _remove_dups_flatten(parameters)
+    if len(parameters) == 1:
+        return parameters[0]
+    return _GenericAlias(self, parameters)
+
+ at _SpecialForm
+def Optional(self, parameters):
     """Optional type.
 
     Optional[X] is equivalent to Union[X, None].
-    """)
+    """
+    arg = _type_check(parameters, f"{self} requires a single type.")
+    return Union[arg, type(None)]
 
-Literal = _SpecialForm('Literal', doc=
+ at _SpecialForm
+def Literal(self, parameters):
     """Special typing form to define literal types (a.k.a. value types).
 
     This form can be used to indicate to type checkers that the corresponding
@@ -486,10 +467,13 @@ def open_helper(file: str, mode: MODE) -> str:
       open_helper('/some/path', 'r')  # Passes type check
       open_helper('/other/path', 'typo')  # Error in type checker
 
-   Literal[...] cannot be subclassed. At runtime, an arbitrary value
-   is allowed as type argument to Literal[...], but type checkers may
-   impose restrictions.
-    """)
+    Literal[...] cannot be subclassed. At runtime, an arbitrary value
+    is allowed as type argument to Literal[...], but type checkers may
+    impose restrictions.
+    """
+    # There is no '_type_check' call because arguments to Literal[...] are
+    # values, not types.
+    return _GenericAlias(self, parameters)
 
 
 class ForwardRef(_Final, _root=True):



More information about the Python-checkins mailing list