diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 5c1e907070ee41..2c0958eab1f5fa 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4947,9 +4947,10 @@ def _source(self): def test_multiple_inheritance(self): class A: pass - with self.assertRaises(TypeError): - class X(NamedTuple, A): - x: int + class X(NamedTuple, A): + x: int + class Y(X, A): + y: int def test_namedtuple_keyword_usage(self): LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) @@ -5282,6 +5283,11 @@ def test_get_type_hints(self): {'a': typing.Optional[int], 'b': int} ) + def test_generic_subclasses(self): + T = TypeVar("T") + class GenericNamedTuple(NamedTuple, Generic[T]): + pass + class IOTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index abb8bcefc5c04a..42f14e311f6783 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2601,7 +2601,9 @@ def _make_nmtuple(name, types, module, defaults = ()): for n, t in types} nm_tpl = collections.namedtuple(name, fields, defaults=defaults, module=module) - nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = types + nm_tpl.__annotations__ = types + nm_tpl.__new__.__annotations__ = types + nm_tpl.__new__.__defaults__ = tuple(defaults) return nm_tpl @@ -2616,7 +2618,9 @@ def _make_nmtuple(name, types, module, defaults = ()): class NamedTupleMeta(type): def __new__(cls, typename, bases, ns): - assert bases[0] is _NamedTuple + if ns.get('_root', False): + return super().__new__(cls, typename, bases, ns) + assert NamedTuple in bases types = ns.get('__annotations__', {}) default_names = [] for field_name in types: @@ -2639,7 +2643,7 @@ def __new__(cls, typename, bases, ns): return nm_tpl -def NamedTuple(typename, fields=None, /, **kwargs): +class NamedTuple(metaclass=NamedTupleMeta): """Typed version of namedtuple. Usage in Python versions >= 3.6:: @@ -2663,22 +2667,15 @@ class Employee(NamedTuple): Employee = NamedTuple('Employee', [('name', str), ('id', int)]) """ - if fields is None: - fields = kwargs.items() - elif kwargs: - raise TypeError("Either list of fields or keywords" - " can be provided to NamedTuple, not both") - return _make_nmtuple(typename, fields, module=_caller()) - -_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {}) - -def _namedtuple_mro_entries(bases): - if len(bases) > 1: - raise TypeError("Multiple inheritance with NamedTuple is not supported") - assert bases[0] is NamedTuple - return (_NamedTuple,) - -NamedTuple.__mro_entries__ = _namedtuple_mro_entries + _root = True + + def __new__(cls, typename, fields=None, /, **kwargs): + if fields is None: + fields = kwargs.items() + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + return _make_nmtuple(typename, fields, module=_caller()) class _TypedDictMeta(type): diff --git a/Misc/NEWS.d/next/Library/2022-03-08-20-13-08.bpo-43923.gefhqh.rst b/Misc/NEWS.d/next/Library/2022-03-08-20-13-08.bpo-43923.gefhqh.rst new file mode 100644 index 00000000000000..d849cf05eaaa7f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-03-08-20-13-08.bpo-43923.gefhqh.rst @@ -0,0 +1,2 @@ +Multiple inheritance with :class:`typing.NamedTuple` now no longer raises an +error.