bpo-40185: Refactor typing.NamedTuple (GH-19371)
This commit is contained in:
parent
307b9d0144
commit
a2ec06938f
|
@ -3598,11 +3598,9 @@ class NamedTupleTests(BaseTestCase):
|
||||||
self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
|
self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
|
||||||
|
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
exec("""
|
class NonDefaultAfterDefault(NamedTuple):
|
||||||
class NonDefaultAfterDefault(NamedTuple):
|
x: int = 3
|
||||||
x: int = 3
|
y: int
|
||||||
y: int
|
|
||||||
""")
|
|
||||||
|
|
||||||
def test_annotation_usage_with_methods(self):
|
def test_annotation_usage_with_methods(self):
|
||||||
self.assertEqual(XMeth(1).double(), 2)
|
self.assertEqual(XMeth(1).double(), 2)
|
||||||
|
@ -3611,20 +3609,16 @@ class NonDefaultAfterDefault(NamedTuple):
|
||||||
self.assertEqual(XRepr(1, 2) + XRepr(3), 0)
|
self.assertEqual(XRepr(1, 2) + XRepr(3), 0)
|
||||||
|
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
exec("""
|
class XMethBad(NamedTuple):
|
||||||
class XMethBad(NamedTuple):
|
x: int
|
||||||
x: int
|
def _fields(self):
|
||||||
def _fields(self):
|
return 'no chance for this'
|
||||||
return 'no chance for this'
|
|
||||||
""")
|
|
||||||
|
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
exec("""
|
class XMethBad2(NamedTuple):
|
||||||
class XMethBad2(NamedTuple):
|
x: int
|
||||||
x: int
|
def _source(self):
|
||||||
def _source(self):
|
return 'no chance for this as well'
|
||||||
return 'no chance for this as well'
|
|
||||||
""")
|
|
||||||
|
|
||||||
def test_multiple_inheritance(self):
|
def test_multiple_inheritance(self):
|
||||||
class A:
|
class A:
|
||||||
|
|
|
@ -1702,51 +1702,41 @@ class SupportsRound(Protocol[T_co]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _make_nmtuple(name, types):
|
def _make_nmtuple(name, types, module, defaults = ()):
|
||||||
msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
|
fields = [n for n, t in types]
|
||||||
types = [(n, _type_check(t, msg)) for n, t in types]
|
types = {n: _type_check(t, f"field {n} annotation must be a type")
|
||||||
nm_tpl = collections.namedtuple(name, [n for n, t in types])
|
for n, t in types}
|
||||||
nm_tpl.__annotations__ = dict(types)
|
nm_tpl = collections.namedtuple(name, fields,
|
||||||
try:
|
defaults=defaults, module=module)
|
||||||
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
|
nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = types
|
||||||
except (AttributeError, ValueError):
|
|
||||||
pass
|
|
||||||
return nm_tpl
|
return nm_tpl
|
||||||
|
|
||||||
|
|
||||||
# attributes prohibited to set in NamedTuple class syntax
|
# attributes prohibited to set in NamedTuple class syntax
|
||||||
_prohibited = {'__new__', '__init__', '__slots__', '__getnewargs__',
|
_prohibited = frozenset({'__new__', '__init__', '__slots__', '__getnewargs__',
|
||||||
'_fields', '_field_defaults',
|
'_fields', '_field_defaults',
|
||||||
'_make', '_replace', '_asdict', '_source'}
|
'_make', '_replace', '_asdict', '_source'})
|
||||||
|
|
||||||
_special = {'__module__', '__name__', '__annotations__'}
|
_special = frozenset({'__module__', '__name__', '__annotations__'})
|
||||||
|
|
||||||
|
|
||||||
class NamedTupleMeta(type):
|
class NamedTupleMeta(type):
|
||||||
|
|
||||||
def __new__(cls, typename, bases, ns):
|
def __new__(cls, typename, bases, ns):
|
||||||
if ns.get('_root', False):
|
assert bases[0] is _NamedTuple
|
||||||
return super().__new__(cls, typename, bases, ns)
|
|
||||||
if len(bases) > 1:
|
|
||||||
raise TypeError("Multiple inheritance with NamedTuple is not supported")
|
|
||||||
assert bases[0] is NamedTuple
|
|
||||||
types = ns.get('__annotations__', {})
|
types = ns.get('__annotations__', {})
|
||||||
nm_tpl = _make_nmtuple(typename, types.items())
|
default_names = []
|
||||||
defaults = []
|
|
||||||
defaults_dict = {}
|
|
||||||
for field_name in types:
|
for field_name in types:
|
||||||
if field_name in ns:
|
if field_name in ns:
|
||||||
default_value = ns[field_name]
|
default_names.append(field_name)
|
||||||
defaults.append(default_value)
|
elif default_names:
|
||||||
defaults_dict[field_name] = default_value
|
raise TypeError(f"Non-default namedtuple field {field_name} "
|
||||||
elif defaults:
|
f"cannot follow default field"
|
||||||
raise TypeError("Non-default namedtuple field {field_name} cannot "
|
f"{'s' if len(default_names) > 1 else ''} "
|
||||||
"follow default field(s) {default_names}"
|
f"{', '.join(default_names)}")
|
||||||
.format(field_name=field_name,
|
nm_tpl = _make_nmtuple(typename, types.items(),
|
||||||
default_names=', '.join(defaults_dict.keys())))
|
defaults=[ns[n] for n in default_names],
|
||||||
nm_tpl.__new__.__annotations__ = dict(types)
|
module=ns['__module__'])
|
||||||
nm_tpl.__new__.__defaults__ = tuple(defaults)
|
|
||||||
nm_tpl._field_defaults = defaults_dict
|
|
||||||
# update from user namespace without overriding special namedtuple attributes
|
# update from user namespace without overriding special namedtuple attributes
|
||||||
for key in ns:
|
for key in ns:
|
||||||
if key in _prohibited:
|
if key in _prohibited:
|
||||||
|
@ -1756,7 +1746,7 @@ class NamedTupleMeta(type):
|
||||||
return nm_tpl
|
return nm_tpl
|
||||||
|
|
||||||
|
|
||||||
class NamedTuple(metaclass=NamedTupleMeta):
|
def NamedTuple(typename, fields=None, /, **kwargs):
|
||||||
"""Typed version of namedtuple.
|
"""Typed version of namedtuple.
|
||||||
|
|
||||||
Usage in Python versions >= 3.6::
|
Usage in Python versions >= 3.6::
|
||||||
|
@ -1780,15 +1770,26 @@ class NamedTuple(metaclass=NamedTupleMeta):
|
||||||
|
|
||||||
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
|
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
|
||||||
"""
|
"""
|
||||||
_root = True
|
if fields is None:
|
||||||
|
fields = kwargs.items()
|
||||||
|
elif kwargs:
|
||||||
|
raise TypeError("Either list of fields or keywords"
|
||||||
|
" can be provided to NamedTuple, not both")
|
||||||
|
try:
|
||||||
|
module = sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
module = None
|
||||||
|
return _make_nmtuple(typename, fields, module=module)
|
||||||
|
|
||||||
def __new__(cls, typename, fields=None, /, **kwargs):
|
_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})
|
||||||
if fields is None:
|
|
||||||
fields = kwargs.items()
|
def _namedtuple_mro_entries(bases):
|
||||||
elif kwargs:
|
if len(bases) > 1:
|
||||||
raise TypeError("Either list of fields or keywords"
|
raise TypeError("Multiple inheritance with NamedTuple is not supported")
|
||||||
" can be provided to NamedTuple, not both")
|
assert bases[0] is NamedTuple
|
||||||
return _make_nmtuple(typename, fields)
|
return (_NamedTuple,)
|
||||||
|
|
||||||
|
NamedTuple.__mro_entries__ = _namedtuple_mro_entries
|
||||||
|
|
||||||
|
|
||||||
def _dict_new(cls, /, *args, **kwargs):
|
def _dict_new(cls, /, *args, **kwargs):
|
||||||
|
|
Loading…
Reference in New Issue