[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