Skip to content

Commit 40ded94

Browse files
bpo-40336: Refactor typing._SpecialForm (GH-19620)
1 parent d663d34 commit 40ded94

File tree

1 file changed

+61
-77
lines changed

1 file changed

+61
-77
lines changed

Lib/typing.py

Lines changed: 61 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,9 @@ def _type_check(arg, msg, is_argument=True):
141141
if (isinstance(arg, _GenericAlias) and
142142
arg.__origin__ in invalid_generic_forms):
143143
raise TypeError(f"{arg} is not valid as type argument")
144-
if (isinstance(arg, _SpecialForm) and arg not in (Any, NoReturn) or
145-
arg in (Generic, Protocol)):
144+
if arg in (Any, NoReturn):
145+
return arg
146+
if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol):
146147
raise TypeError(f"Plain {arg} is not valid as type argument")
147148
if isinstance(arg, (type, TypeVar, ForwardRef)):
148149
return arg
@@ -299,41 +300,18 @@ def __deepcopy__(self, memo):
299300
return self
300301

301302

302-
class _SpecialForm(_Final, _Immutable, _root=True):
303-
"""Internal indicator of special typing constructs.
304-
See _doc instance attribute for specific docs.
305-
"""
306-
307-
__slots__ = ('_name', '_doc')
308-
309-
def __new__(cls, *args, **kwds):
310-
"""Constructor.
311-
312-
This only exists to give a better error message in case
313-
someone tries to subclass a special typing object (not a good idea).
314-
"""
315-
if (len(args) == 3 and
316-
isinstance(args[0], str) and
317-
isinstance(args[1], tuple)):
318-
# Close enough.
319-
raise TypeError(f"Cannot subclass {cls!r}")
320-
return super().__new__(cls)
321-
322-
def __init__(self, name, doc):
323-
self._name = name
324-
self._doc = doc
325-
326-
@property
327-
def __doc__(self):
328-
return self._doc
303+
# Internal indicator of special typing constructs.
304+
# See __doc__ instance attribute for specific docs.
305+
class _SpecialForm(_Final, _root=True):
306+
__slots__ = ('_name', '__doc__', '_getitem')
329307

330-
def __eq__(self, other):
331-
if not isinstance(other, _SpecialForm):
332-
return NotImplemented
333-
return self._name == other._name
308+
def __init__(self, getitem):
309+
self._getitem = getitem
310+
self._name = getitem.__name__
311+
self.__doc__ = getitem.__doc__
334312

335-
def __hash__(self):
336-
return hash((self._name,))
313+
def __mro_entries__(self, bases):
314+
raise TypeError(f"Cannot subclass {self!r}")
337315

338316
def __repr__(self):
339317
return 'typing.' + self._name
@@ -352,31 +330,10 @@ def __subclasscheck__(self, cls):
352330

353331
@_tp_cache
354332
def __getitem__(self, parameters):
355-
if self._name in ('ClassVar', 'Final'):
356-
item = _type_check(parameters, f'{self._name} accepts only single type.')
357-
return _GenericAlias(self, (item,))
358-
if self._name == 'Union':
359-
if parameters == ():
360-
raise TypeError("Cannot take a Union of no types.")
361-
if not isinstance(parameters, tuple):
362-
parameters = (parameters,)
363-
msg = "Union[arg, ...]: each arg must be a type."
364-
parameters = tuple(_type_check(p, msg) for p in parameters)
365-
parameters = _remove_dups_flatten(parameters)
366-
if len(parameters) == 1:
367-
return parameters[0]
368-
return _GenericAlias(self, parameters)
369-
if self._name == 'Optional':
370-
arg = _type_check(parameters, "Optional[t] requires a single type.")
371-
return Union[arg, type(None)]
372-
if self._name == 'Literal':
373-
# There is no '_type_check' call because arguments to Literal[...] are
374-
# values, not types.
375-
return _GenericAlias(self, parameters)
376-
raise TypeError(f"{self} is not subscriptable")
377-
378-
379-
Any = _SpecialForm('Any', doc=
333+
return self._getitem(self, parameters)
334+
335+
@_SpecialForm
336+
def Any(self, parameters):
380337
"""Special type indicating an unconstrained type.
381338
382339
- Any is compatible with every type.
@@ -386,9 +343,11 @@ def __getitem__(self, parameters):
386343
Note that all the above statements are true from the point of view of
387344
static type checkers. At runtime, Any should not be used with instance
388345
or class checks.
389-
""")
346+
"""
347+
raise TypeError(f"{self} is not subscriptable")
390348

391-
NoReturn = _SpecialForm('NoReturn', doc=
349+
@_SpecialForm
350+
def NoReturn(self, parameters):
392351
"""Special type indicating functions that never return.
393352
Example::
394353
@@ -399,9 +358,11 @@ def stop() -> NoReturn:
399358
400359
This type is invalid in other positions, e.g., ``List[NoReturn]``
401360
will fail in static type checkers.
402-
""")
361+
"""
362+
raise TypeError(f"{self} is not subscriptable")
403363

404-
ClassVar = _SpecialForm('ClassVar', doc=
364+
@_SpecialForm
365+
def ClassVar(self, parameters):
405366
"""Special type construct to mark class variables.
406367
407368
An annotation wrapped in ClassVar indicates that a given
@@ -416,9 +377,12 @@ class Starship:
416377
417378
Note that ClassVar is not a class itself, and should not
418379
be used with isinstance() or issubclass().
419-
""")
380+
"""
381+
item = _type_check(parameters, f'{self} accepts only single type.')
382+
return _GenericAlias(self, (item,))
420383

421-
Final = _SpecialForm('Final', doc=
384+
@_SpecialForm
385+
def Final(self, parameters):
422386
"""Special typing construct to indicate final names to type checkers.
423387
424388
A final name cannot be re-assigned or overridden in a subclass.
@@ -434,9 +398,12 @@ class FastConnector(Connection):
434398
TIMEOUT = 1 # Error reported by type checker
435399
436400
There is no runtime checking of these properties.
437-
""")
401+
"""
402+
item = _type_check(parameters, f'{self} accepts only single type.')
403+
return _GenericAlias(self, (item,))
438404

439-
Union = _SpecialForm('Union', doc=
405+
@_SpecialForm
406+
def Union(self, parameters):
440407
"""Union type; Union[X, Y] means either X or Y.
441408
442409
To define a union, use e.g. Union[int, str]. Details:
@@ -461,15 +428,29 @@ class FastConnector(Connection):
461428
462429
- You cannot subclass or instantiate a union.
463430
- You can use Optional[X] as a shorthand for Union[X, None].
464-
""")
465-
466-
Optional = _SpecialForm('Optional', doc=
431+
"""
432+
if parameters == ():
433+
raise TypeError("Cannot take a Union of no types.")
434+
if not isinstance(parameters, tuple):
435+
parameters = (parameters,)
436+
msg = "Union[arg, ...]: each arg must be a type."
437+
parameters = tuple(_type_check(p, msg) for p in parameters)
438+
parameters = _remove_dups_flatten(parameters)
439+
if len(parameters) == 1:
440+
return parameters[0]
441+
return _GenericAlias(self, parameters)
442+
443+
@_SpecialForm
444+
def Optional(self, parameters):
467445
"""Optional type.
468446
469447
Optional[X] is equivalent to Union[X, None].
470-
""")
448+
"""
449+
arg = _type_check(parameters, f"{self} requires a single type.")
450+
return Union[arg, type(None)]
471451

472-
Literal = _SpecialForm('Literal', doc=
452+
@_SpecialForm
453+
def Literal(self, parameters):
473454
"""Special typing form to define literal types (a.k.a. value types).
474455
475456
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:
486467
open_helper('/some/path', 'r') # Passes type check
487468
open_helper('/other/path', 'typo') # Error in type checker
488469
489-
Literal[...] cannot be subclassed. At runtime, an arbitrary value
490-
is allowed as type argument to Literal[...], but type checkers may
491-
impose restrictions.
492-
""")
470+
Literal[...] cannot be subclassed. At runtime, an arbitrary value
471+
is allowed as type argument to Literal[...], but type checkers may
472+
impose restrictions.
473+
"""
474+
# There is no '_type_check' call because arguments to Literal[...] are
475+
# values, not types.
476+
return _GenericAlias(self, parameters)
493477

494478

495479
class ForwardRef(_Final, _root=True):

0 commit comments

Comments
 (0)