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

This commit is contained in:
Serhiy Storchaka 2020-04-23 21:26:48 +03:00 committed by GitHub
parent d663d34685
commit 40ded947f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 59 additions and 75 deletions

View File

@ -141,8 +141,9 @@ def _type_check(arg, msg, is_argument=True):
if (isinstance(arg, _GenericAlias) and if (isinstance(arg, _GenericAlias) and
arg.__origin__ in invalid_generic_forms): arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument") raise TypeError(f"{arg} is not valid as type argument")
if (isinstance(arg, _SpecialForm) and arg not in (Any, NoReturn) or if arg in (Any, NoReturn):
arg in (Generic, Protocol)): return arg
if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol):
raise TypeError(f"Plain {arg} is not valid as type argument") raise TypeError(f"Plain {arg} is not valid as type argument")
if isinstance(arg, (type, TypeVar, ForwardRef)): if isinstance(arg, (type, TypeVar, ForwardRef)):
return arg return arg
@ -299,41 +300,18 @@ class _Immutable:
return self return self
class _SpecialForm(_Final, _Immutable, _root=True): # Internal indicator of special typing constructs.
"""Internal indicator of special typing constructs. # See __doc__ instance attribute for specific docs.
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): def __mro_entries__(self, bases):
"""Constructor. raise TypeError(f"Cannot subclass {self!r}")
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 __repr__(self): def __repr__(self):
return 'typing.' + self._name return 'typing.' + self._name
@ -352,31 +330,10 @@ class _SpecialForm(_Final, _Immutable, _root=True):
@_tp_cache @_tp_cache
def __getitem__(self, parameters): def __getitem__(self, parameters):
if self._name in ('ClassVar', 'Final'): return self._getitem(self, parameters)
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")
@_SpecialForm
Any = _SpecialForm('Any', doc= def Any(self, parameters):
"""Special type indicating an unconstrained type. """Special type indicating an unconstrained type.
- Any is compatible with every 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 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 static type checkers. At runtime, Any should not be used with instance
or class checks. 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. """Special type indicating functions that never return.
Example:: Example::
@ -399,9 +358,11 @@ NoReturn = _SpecialForm('NoReturn', doc=
This type is invalid in other positions, e.g., ``List[NoReturn]`` This type is invalid in other positions, e.g., ``List[NoReturn]``
will fail in static type checkers. 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. """Special type construct to mark class variables.
An annotation wrapped in ClassVar indicates that a given 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 Note that ClassVar is not a class itself, and should not
be used with isinstance() or issubclass(). 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. """Special typing construct to indicate final names to type checkers.
A final name cannot be re-assigned or overridden in a subclass. 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 TIMEOUT = 1 # Error reported by type checker
There is no runtime checking of these properties. 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. """Union type; Union[X, Y] means either X or Y.
To define a union, use e.g. Union[int, str]. Details: 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 cannot subclass or instantiate a union.
- You can use Optional[X] as a shorthand for Union[X, None]. - 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 type.
Optional[X] is equivalent to Union[X, None]. 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). """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 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('/some/path', 'r') # Passes type check
open_helper('/other/path', 'typo') # Error in type checker open_helper('/other/path', 'typo') # Error in type checker
Literal[...] cannot be subclassed. At runtime, an arbitrary value Literal[...] cannot be subclassed. At runtime, an arbitrary value
is allowed as type argument to Literal[...], but type checkers may is allowed as type argument to Literal[...], but type checkers may
impose restrictions. 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): class ForwardRef(_Final, _root=True):