Commit e09ca3c3 authored by Guido van Rossum's avatar Guido van Rossum

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

parent abcf558b
...@@ -1865,6 +1865,20 @@ class NamedTupleTests(BaseTestCase): ...@@ -1865,6 +1865,20 @@ class NamedTupleTests(BaseTestCase):
self.assertEqual(CoolEmployee._fields, ('name', 'cool')) self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int)) 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): def test_pickle(self):
global Emp # pickle wants to reference the class by name global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('id', int)]) Emp = NamedTuple('Emp', [('name', str), ('id', int)])
......
...@@ -1875,6 +1875,8 @@ class Type(Generic[CT_co], extra=type): ...@@ -1875,6 +1875,8 @@ class Type(Generic[CT_co], extra=type):
def _make_nmtuple(name, types): 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 = collections.namedtuple(name, [n for n, t in types])
nm_tpl._field_types = dict(types) nm_tpl._field_types = dict(types)
try: try:
...@@ -1884,19 +1886,24 @@ def _make_nmtuple(name, types): ...@@ -1884,19 +1886,24 @@ def _make_nmtuple(name, types):
return nm_tpl return nm_tpl
if sys.version_info[:2] >= (3, 6): _PY36 = sys.version_info[:2] >= (3, 6)
class NamedTupleMeta(type):
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) 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__', {}) types = ns.get('__annotations__', {})
return _make_nmtuple(typename, types.items()) return _make_nmtuple(typename, types.items())
class NamedTuple(metaclass=NamedTupleMeta, _root=True): class NamedTuple(metaclass=NamedTupleMeta):
"""Typed version of namedtuple. """Typed version of namedtuple.
Usage:: Usage in Python versions >= 3.6::
class Employee(NamedTuple): class Employee(NamedTuple):
name: str name: str
...@@ -1909,30 +1916,25 @@ if sys.version_info[:2] >= (3, 6): ...@@ -1909,30 +1916,25 @@ if sys.version_info[:2] >= (3, 6):
The resulting class has one extra attribute: _field_types, The resulting class has one extra attribute: _field_types,
giving a dict mapping field names to types. (The field names giving a dict mapping field names to types. (The field names
are in the _fields attribute, which is part of the namedtuple 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)])
"""
def __new__(self, typename, fields): Employee = NamedTuple('Employee', name=str, id=int)
return _make_nmtuple(typename, fields)
else:
def NamedTuple(typename, fields):
"""Typed version of namedtuple.
Usage:: In Python versions <= 3.5 use::
Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)]) Employee = 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.)
""" """
_root = True
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) return _make_nmtuple(typename, fields)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment