Issue #28556: Allow keyword syntax for NamedTuple (Ivan Levkivskyi) (upstream #321)

This commit is contained in:
Guido van Rossum 2016-11-15 09:48:06 -08:00
parent 49e8f2d204
commit 2f84144235
2 changed files with 52 additions and 36 deletions

View File

@ -1865,6 +1865,20 @@ class NamedTupleTests(BaseTestCase):
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int))
@skipUnless(PY36, 'Python 3.6 required')
def test_namedtuple_keyword_usage(self):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
nick = LocalEmployee('Nick', 25)
self.assertIsInstance(nick, tuple)
self.assertEqual(nick.name, 'Nick')
self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
self.assertEqual(LocalEmployee._fields, ('name', 'age'))
self.assertEqual(LocalEmployee._field_types, dict(name=str, age=int))
with self.assertRaises(TypeError):
NamedTuple('Name', [('x', int)], y=str)
with self.assertRaises(TypeError):
NamedTuple('Name', x=1, y='a')
def test_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('id', int)])

View File

@ -1875,6 +1875,8 @@ class Type(Generic[CT_co], extra=type):
def _make_nmtuple(name, types):
msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
types = [(n, _type_check(t, msg)) for n, t in types]
nm_tpl = collections.namedtuple(name, [n for n, t in types])
nm_tpl._field_types = dict(types)
try:
@ -1884,19 +1886,24 @@ def _make_nmtuple(name, types):
return nm_tpl
if sys.version_info[:2] >= (3, 6):
class NamedTupleMeta(type):
_PY36 = sys.version_info[:2] >= (3, 6)
def __new__(cls, typename, bases, ns, *, _root=False):
if _root:
class NamedTupleMeta(type):
def __new__(cls, typename, bases, ns):
if ns.get('_root', False):
return super().__new__(cls, typename, bases, ns)
if not _PY36:
raise TypeError("Class syntax for NamedTuple is only supported"
" in Python 3.6+")
types = ns.get('__annotations__', {})
return _make_nmtuple(typename, types.items())
class NamedTuple(metaclass=NamedTupleMeta, _root=True):
class NamedTuple(metaclass=NamedTupleMeta):
"""Typed version of namedtuple.
Usage::
Usage in Python versions >= 3.6::
class Employee(NamedTuple):
name: str
@ -1909,30 +1916,25 @@ if sys.version_info[:2] >= (3, 6):
The resulting class has one extra attribute: _field_types,
giving a dict mapping field names to types. (The field names
are in the _fields attribute, which is part of the namedtuple
API.) Backward-compatible usage::
API.) Alternative equivalent keyword syntax is also accepted::
Employee = NamedTuple('Employee', name=str, id=int)
In Python versions <= 3.5 use::
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
_root = True
def __new__(self, typename, fields):
return _make_nmtuple(typename, fields)
else:
def NamedTuple(typename, fields):
"""Typed version of namedtuple.
Usage::
Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
This is equivalent to::
Employee = collections.namedtuple('Employee', ['name', 'id'])
The resulting class has one extra attribute: _field_types,
giving a dict mapping field names to types. (The field names
are in the _fields attribute, which is part of the namedtuple
API.)
"""
def __new__(self, typename, fields=None, **kwargs):
if kwargs and not _PY36:
raise TypeError("Keyword syntax for NamedTuple is only supported"
" in Python 3.6+")
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)