From 40ded947f82683f0ed08144599a83d3a1ce719eb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 23 Apr 2020 21:26:48 +0300 Subject: [PATCH] bpo-40336: Refactor typing._SpecialForm (GH-19620) --- Lib/typing.py | 134 ++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 75 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 9383fb8ff3a..0dcf291950f 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 @@ class _Immutable: return self -class _SpecialForm(_Final, _Immutable, _root=True): - """Internal indicator of special typing constructs. - See _doc instance attribute for specific docs. - """ +# Internal indicator of special typing constructs. +# See __doc__ instance attribute for specific docs. +class _SpecialForm(_Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') - __slots__ = ('_name', '_doc') + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__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 - - def __eq__(self, other): - if not isinstance(other, _SpecialForm): - return NotImplemented - return self._name == other._name - - 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 @@ class _SpecialForm(_Final, _Immutable, _root=True): @_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") + return self._getitem(self, parameters) - -Any = _SpecialForm('Any', doc= +@_SpecialForm +def Any(self, parameters): """Special type indicating an unconstrained type. - Any is compatible with every type. @@ -386,9 +343,11 @@ Any = _SpecialForm('Any', doc= 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= +@_SpecialForm +def NoReturn(self, parameters): """Special type indicating functions that never return. Example:: @@ -399,9 +358,11 @@ NoReturn = _SpecialForm('NoReturn', doc= 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= +@_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 @@ ClassVar = _SpecialForm('ClassVar', doc= 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= +@_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 @@ Final = _SpecialForm('Final', doc= 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= +@_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 @@ Union = _SpecialForm('Union', doc= - You cannot subclass or instantiate a union. - You can use Optional[X] as a shorthand for Union[X, None]. - """) + """ + 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) -Optional = _SpecialForm('Optional', doc= +@_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= +@_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 @@ Literal = _SpecialForm('Literal', doc= 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):