Skip to content

Commit 03f1573

Browse files
committed
Revert "bpo-40185: Refactor typing.NamedTuple (pythonGH-19371)"
This reverts commit a2ec069.
1 parent 8f31bf4 commit 03f1573

File tree

2 files changed

+59
-50
lines changed

2 files changed

+59
-50
lines changed

Lib/test/test_typing.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4441,9 +4441,11 @@ def test_annotation_usage_with_default(self):
44414441
self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
44424442

44434443
with self.assertRaises(TypeError):
4444-
class NonDefaultAfterDefault(NamedTuple):
4445-
x: int = 3
4446-
y: int
4444+
exec("""
4445+
class NonDefaultAfterDefault(NamedTuple):
4446+
x: int = 3
4447+
y: int
4448+
""")
44474449

44484450
def test_annotation_usage_with_methods(self):
44494451
self.assertEqual(XMeth(1).double(), 2)
@@ -4452,16 +4454,20 @@ def test_annotation_usage_with_methods(self):
44524454
self.assertEqual(XRepr(1, 2) + XRepr(3), 0)
44534455

44544456
with self.assertRaises(AttributeError):
4455-
class XMethBad(NamedTuple):
4456-
x: int
4457-
def _fields(self):
4458-
return 'no chance for this'
4457+
exec("""
4458+
class XMethBad(NamedTuple):
4459+
x: int
4460+
def _fields(self):
4461+
return 'no chance for this'
4462+
""")
44594463

44604464
with self.assertRaises(AttributeError):
4461-
class XMethBad2(NamedTuple):
4462-
x: int
4463-
def _source(self):
4464-
return 'no chance for this as well'
4465+
exec("""
4466+
class XMethBad2(NamedTuple):
4467+
x: int
4468+
def _source(self):
4469+
return 'no chance for this as well'
4470+
""")
44654471

44664472
def test_multiple_inheritance(self):
44674473
class A:

Lib/typing.py

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,41 +2396,51 @@ def __round__(self, ndigits: int = 0) -> T_co:
23962396
pass
23972397

23982398

2399-
def _make_nmtuple(name, types, module, defaults = ()):
2400-
fields = [n for n, t in types]
2401-
types = {n: _type_check(t, f"field {n} annotation must be a type")
2402-
for n, t in types}
2403-
nm_tpl = collections.namedtuple(name, fields,
2404-
defaults=defaults, module=module)
2405-
nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = types
2399+
def _make_nmtuple(name, types):
2400+
msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
2401+
types = [(n, _type_check(t, msg)) for n, t in types]
2402+
nm_tpl = collections.namedtuple(name, [n for n, t in types])
2403+
nm_tpl.__annotations__ = dict(types)
2404+
try:
2405+
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
2406+
except (AttributeError, ValueError):
2407+
pass
24062408
return nm_tpl
24072409

24082410

24092411
# attributes prohibited to set in NamedTuple class syntax
2410-
_prohibited = frozenset({'__new__', '__init__', '__slots__', '__getnewargs__',
2411-
'_fields', '_field_defaults',
2412-
'_make', '_replace', '_asdict', '_source'})
2412+
_prohibited = {'__new__', '__init__', '__slots__', '__getnewargs__',
2413+
'_fields', '_field_defaults',
2414+
'_make', '_replace', '_asdict', '_source'}
24132415

2414-
_special = frozenset({'__module__', '__name__', '__annotations__'})
2416+
_special = {'__module__', '__name__', '__annotations__'}
24152417

24162418

24172419
class NamedTupleMeta(type):
24182420

24192421
def __new__(cls, typename, bases, ns):
2420-
assert bases[0] is _NamedTuple
2422+
if ns.get('_root', False):
2423+
return super().__new__(cls, typename, bases, ns)
2424+
if len(bases) > 1:
2425+
raise TypeError("Multiple inheritance with NamedTuple is not supported")
2426+
assert bases[0] is NamedTuple
24212427
types = ns.get('__annotations__', {})
2422-
default_names = []
2428+
nm_tpl = _make_nmtuple(typename, types.items())
2429+
defaults = []
2430+
defaults_dict = {}
24232431
for field_name in types:
24242432
if field_name in ns:
2425-
default_names.append(field_name)
2426-
elif default_names:
2427-
raise TypeError(f"Non-default namedtuple field {field_name} "
2428-
f"cannot follow default field"
2429-
f"{'s' if len(default_names) > 1 else ''} "
2430-
f"{', '.join(default_names)}")
2431-
nm_tpl = _make_nmtuple(typename, types.items(),
2432-
defaults=[ns[n] for n in default_names],
2433-
module=ns['__module__'])
2433+
default_value = ns[field_name]
2434+
defaults.append(default_value)
2435+
defaults_dict[field_name] = default_value
2436+
elif defaults:
2437+
raise TypeError("Non-default namedtuple field {field_name} cannot "
2438+
"follow default field(s) {default_names}"
2439+
.format(field_name=field_name,
2440+
default_names=', '.join(defaults_dict.keys())))
2441+
nm_tpl.__new__.__annotations__ = dict(types)
2442+
nm_tpl.__new__.__defaults__ = tuple(defaults)
2443+
nm_tpl._field_defaults = defaults_dict
24342444
# update from user namespace without overriding special namedtuple attributes
24352445
for key in ns:
24362446
if key in _prohibited:
@@ -2440,7 +2450,7 @@ def __new__(cls, typename, bases, ns):
24402450
return nm_tpl
24412451

24422452

2443-
def NamedTuple(typename, fields=None, /, **kwargs):
2453+
class NamedTuple(metaclass=NamedTupleMeta):
24442454
"""Typed version of namedtuple.
24452455
24462456
Usage in Python versions >= 3.6::
@@ -2464,22 +2474,15 @@ class Employee(NamedTuple):
24642474
24652475
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
24662476
"""
2467-
if fields is None:
2468-
fields = kwargs.items()
2469-
elif kwargs:
2470-
raise TypeError("Either list of fields or keywords"
2471-
" can be provided to NamedTuple, not both")
2472-
return _make_nmtuple(typename, fields, module=_caller())
2473-
2474-
_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})
2475-
2476-
def _namedtuple_mro_entries(bases):
2477-
if len(bases) > 1:
2478-
raise TypeError("Multiple inheritance with NamedTuple is not supported")
2479-
assert bases[0] is NamedTuple
2480-
return (_NamedTuple,)
2481-
2482-
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
2477+
_root = True
2478+
2479+
def __new__(cls, typename, fields=None, /, **kwargs):
2480+
if fields is None:
2481+
fields = kwargs.items()
2482+
elif kwargs:
2483+
raise TypeError("Either list of fields or keywords"
2484+
" can be provided to NamedTuple, not both")
2485+
return _make_nmtuple(typename, fields)
24832486

24842487

24852488
class _TypedDictMeta(type):

0 commit comments

Comments
 (0)