|
37 | 37 | 'Counter',
|
38 | 38 | 'Deque',
|
39 | 39 | 'DefaultDict',
|
| 40 | + 'NamedTuple', |
40 | 41 | 'OrderedDict',
|
41 | 42 | 'TypedDict',
|
42 | 43 |
|
@@ -380,6 +381,46 @@ def _is_callable_members_only(cls):
|
380 | 381 | return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls))
|
381 | 382 |
|
382 | 383 |
|
| 384 | +def _maybe_adjust_parameters(cls): |
| 385 | + """Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__. |
| 386 | +
|
| 387 | + The contents of this function are very similar |
| 388 | + to logic found in typing.Generic.__init_subclass__ |
| 389 | + on the CPython main branch. |
| 390 | + """ |
| 391 | + tvars = [] |
| 392 | + if '__orig_bases__' in cls.__dict__: |
| 393 | + tvars = typing._collect_type_vars(cls.__orig_bases__) |
| 394 | + # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. |
| 395 | + # If found, tvars must be a subset of it. |
| 396 | + # If not found, tvars is it. |
| 397 | + # Also check for and reject plain Generic, |
| 398 | + # and reject multiple Generic[...] and/or Protocol[...]. |
| 399 | + gvars = None |
| 400 | + for base in cls.__orig_bases__: |
| 401 | + if (isinstance(base, typing._GenericAlias) and |
| 402 | + base.__origin__ in (typing.Generic, Protocol)): |
| 403 | + # for error messages |
| 404 | + the_base = base.__origin__.__name__ |
| 405 | + if gvars is not None: |
| 406 | + raise TypeError( |
| 407 | + "Cannot inherit from Generic[...]" |
| 408 | + " and/or Protocol[...] multiple types.") |
| 409 | + gvars = base.__parameters__ |
| 410 | + if gvars is None: |
| 411 | + gvars = tvars |
| 412 | + else: |
| 413 | + tvarset = set(tvars) |
| 414 | + gvarset = set(gvars) |
| 415 | + if not tvarset <= gvarset: |
| 416 | + s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) |
| 417 | + s_args = ', '.join(str(g) for g in gvars) |
| 418 | + raise TypeError(f"Some type variables ({s_vars}) are" |
| 419 | + f" not listed in {the_base}[{s_args}]") |
| 420 | + tvars = gvars |
| 421 | + cls.__parameters__ = tuple(tvars) |
| 422 | + |
| 423 | + |
383 | 424 | # 3.8+
|
384 | 425 | if hasattr(typing, 'Protocol'):
|
385 | 426 | Protocol = typing.Protocol
|
@@ -476,43 +517,13 @@ def __class_getitem__(cls, params):
|
476 | 517 | return typing._GenericAlias(cls, params)
|
477 | 518 |
|
478 | 519 | def __init_subclass__(cls, *args, **kwargs):
|
479 |
| - tvars = [] |
480 | 520 | if '__orig_bases__' in cls.__dict__:
|
481 | 521 | error = typing.Generic in cls.__orig_bases__
|
482 | 522 | else:
|
483 | 523 | error = typing.Generic in cls.__bases__
|
484 | 524 | if error:
|
485 | 525 | raise TypeError("Cannot inherit from plain Generic")
|
486 |
| - if '__orig_bases__' in cls.__dict__: |
487 |
| - tvars = typing._collect_type_vars(cls.__orig_bases__) |
488 |
| - # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. |
489 |
| - # If found, tvars must be a subset of it. |
490 |
| - # If not found, tvars is it. |
491 |
| - # Also check for and reject plain Generic, |
492 |
| - # and reject multiple Generic[...] and/or Protocol[...]. |
493 |
| - gvars = None |
494 |
| - for base in cls.__orig_bases__: |
495 |
| - if (isinstance(base, typing._GenericAlias) and |
496 |
| - base.__origin__ in (typing.Generic, Protocol)): |
497 |
| - # for error messages |
498 |
| - the_base = base.__origin__.__name__ |
499 |
| - if gvars is not None: |
500 |
| - raise TypeError( |
501 |
| - "Cannot inherit from Generic[...]" |
502 |
| - " and/or Protocol[...] multiple types.") |
503 |
| - gvars = base.__parameters__ |
504 |
| - if gvars is None: |
505 |
| - gvars = tvars |
506 |
| - else: |
507 |
| - tvarset = set(tvars) |
508 |
| - gvarset = set(gvars) |
509 |
| - if not tvarset <= gvarset: |
510 |
| - s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) |
511 |
| - s_args = ', '.join(str(g) for g in gvars) |
512 |
| - raise TypeError(f"Some type variables ({s_vars}) are" |
513 |
| - f" not listed in {the_base}[{s_args}]") |
514 |
| - tvars = gvars |
515 |
| - cls.__parameters__ = tuple(tvars) |
| 526 | + _maybe_adjust_parameters(cls) |
516 | 527 |
|
517 | 528 | # Determine if this is a protocol or a concrete subclass.
|
518 | 529 | if not cls.__dict__.get('_is_protocol', None):
|
@@ -613,6 +624,7 @@ def __index__(self) -> int:
|
613 | 624 | # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059
|
614 | 625 | # The standard library TypedDict below Python 3.11 does not store runtime
|
615 | 626 | # information about optional and required keys when using Required or NotRequired.
|
| 627 | + # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11. |
616 | 628 | TypedDict = typing.TypedDict
|
617 | 629 | _TypedDictMeta = typing._TypedDictMeta
|
618 | 630 | is_typeddict = typing.is_typeddict
|
@@ -695,8 +707,16 @@ def __new__(cls, name, bases, ns, total=True):
|
695 | 707 | # Subclasses and instances of TypedDict return actual dictionaries
|
696 | 708 | # via _dict_new.
|
697 | 709 | ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new
|
| 710 | + # Don't insert typing.Generic into __bases__ here, |
| 711 | + # or Generic.__init_subclass__ will raise TypeError |
| 712 | + # in the super().__new__() call. |
| 713 | + # Instead, monkey-patch __bases__ onto the class after it's been created. |
698 | 714 | tp_dict = super().__new__(cls, name, (dict,), ns)
|
699 | 715 |
|
| 716 | + if any(issubclass(base, typing.Generic) for base in bases): |
| 717 | + tp_dict.__bases__ = (typing.Generic, dict) |
| 718 | + _maybe_adjust_parameters(tp_dict) |
| 719 | + |
700 | 720 | annotations = {}
|
701 | 721 | own_annotations = ns.get('__annotations__', {})
|
702 | 722 | msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
|
@@ -1958,3 +1978,92 @@ def decorator(cls_or_fn):
|
1958 | 1978 | if not hasattr(typing, "TypeVarTuple"):
|
1959 | 1979 | typing._collect_type_vars = _collect_type_vars
|
1960 | 1980 | typing._check_generic = _check_generic
|
| 1981 | + |
| 1982 | + |
| 1983 | +# Backport typing.NamedTuple as it exists in Python 3.11. |
| 1984 | +# In 3.11, the ability to define generic `NamedTuple`s was supported. |
| 1985 | +# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8. |
| 1986 | +if sys.version_info >= (3, 11): |
| 1987 | + NamedTuple = typing.NamedTuple |
| 1988 | +else: |
| 1989 | + def _caller(): |
| 1990 | + try: |
| 1991 | + return sys._getframe(2).f_globals.get('__name__', '__main__') |
| 1992 | + except (AttributeError, ValueError): # For platforms without _getframe() |
| 1993 | + return None |
| 1994 | + |
| 1995 | + def _make_nmtuple(name, types, module, defaults=()): |
| 1996 | + fields = [n for n, t in types] |
| 1997 | + annotations = {n: typing._type_check(t, f"field {n} annotation must be a type") |
| 1998 | + for n, t in types} |
| 1999 | + nm_tpl = collections.namedtuple(name, fields, |
| 2000 | + defaults=defaults, module=module) |
| 2001 | + nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations |
| 2002 | + # The `_field_types` attribute was removed in 3.9; |
| 2003 | + # in earlier versions, it is the same as the `__annotations__` attribute |
| 2004 | + if sys.version_info < (3, 9): |
| 2005 | + nm_tpl._field_types = annotations |
| 2006 | + return nm_tpl |
| 2007 | + |
| 2008 | + _prohibited_namedtuple_fields = typing._prohibited |
| 2009 | + _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'}) |
| 2010 | + |
| 2011 | + class _NamedTupleMeta(type): |
| 2012 | + def __new__(cls, typename, bases, ns): |
| 2013 | + assert _NamedTuple in bases |
| 2014 | + for base in bases: |
| 2015 | + if base is not _NamedTuple and base is not typing.Generic: |
| 2016 | + raise TypeError( |
| 2017 | + 'can only inherit from a NamedTuple type and Generic') |
| 2018 | + bases = tuple(tuple if base is _NamedTuple else base for base in bases) |
| 2019 | + types = ns.get('__annotations__', {}) |
| 2020 | + default_names = [] |
| 2021 | + for field_name in types: |
| 2022 | + if field_name in ns: |
| 2023 | + default_names.append(field_name) |
| 2024 | + elif default_names: |
| 2025 | + raise TypeError(f"Non-default namedtuple field {field_name} " |
| 2026 | + f"cannot follow default field" |
| 2027 | + f"{'s' if len(default_names) > 1 else ''} " |
| 2028 | + f"{', '.join(default_names)}") |
| 2029 | + nm_tpl = _make_nmtuple( |
| 2030 | + typename, types.items(), |
| 2031 | + defaults=[ns[n] for n in default_names], |
| 2032 | + module=ns['__module__'] |
| 2033 | + ) |
| 2034 | + nm_tpl.__bases__ = bases |
| 2035 | + if typing.Generic in bases: |
| 2036 | + class_getitem = typing.Generic.__class_getitem__.__func__ |
| 2037 | + nm_tpl.__class_getitem__ = classmethod(class_getitem) |
| 2038 | + # update from user namespace without overriding special namedtuple attributes |
| 2039 | + for key in ns: |
| 2040 | + if key in _prohibited_namedtuple_fields: |
| 2041 | + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) |
| 2042 | + elif key not in _special_namedtuple_fields and key not in nm_tpl._fields: |
| 2043 | + setattr(nm_tpl, key, ns[key]) |
| 2044 | + if typing.Generic in bases: |
| 2045 | + nm_tpl.__init_subclass__() |
| 2046 | + return nm_tpl |
| 2047 | + |
| 2048 | + def NamedTuple(__typename, __fields=None, **kwargs): |
| 2049 | + if __fields is None: |
| 2050 | + __fields = kwargs.items() |
| 2051 | + elif kwargs: |
| 2052 | + raise TypeError("Either list of fields or keywords" |
| 2053 | + " can be provided to NamedTuple, not both") |
| 2054 | + return _make_nmtuple(__typename, __fields, module=_caller()) |
| 2055 | + |
| 2056 | + NamedTuple.__doc__ = typing.NamedTuple.__doc__ |
| 2057 | + _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) |
| 2058 | + |
| 2059 | + # On 3.8+, alter the signature so that it matches typing.NamedTuple. |
| 2060 | + # The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7, |
| 2061 | + # so just leave the signature as it is on 3.7. |
| 2062 | + if sys.version_info >= (3, 8): |
| 2063 | + NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)' |
| 2064 | + |
| 2065 | + def _namedtuple_mro_entries(bases): |
| 2066 | + assert NamedTuple in bases |
| 2067 | + return (_NamedTuple,) |
| 2068 | + |
| 2069 | + NamedTuple.__mro_entries__ = _namedtuple_mro_entries |
0 commit comments