Skip to content

Allow keyword syntax for NamedTuple #321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1848,6 +1848,20 @@ def test_annotation_usage(self):
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)])
Expand Down
76 changes: 39 additions & 37 deletions src/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,8 @@ def new_user(user_class: Type[U]) -> U:


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:
Expand All @@ -1960,55 +1962,55 @@ 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:
return super().__new__(cls, typename, bases, ns)
types = ns.get('__annotations__', {})
return _make_nmtuple(typename, types.items())

class NamedTuple(metaclass=NamedTupleMeta, _root=True):
"""Typed version of namedtuple.
class NamedTupleMeta(type):

Usage::
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 Employee(NamedTuple):
name: str
id: int
class NamedTuple(metaclass=NamedTupleMeta):
"""Typed version of namedtuple.

This is equivalent to::
Usage in Python versions >= 3.6::

Employee = collections.namedtuple('Employee', ['name', 'id'])
class Employee(NamedTuple):
name: str
id: int

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::
This is equivalent to::

Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""

def __new__(self, typename, fields):
return _make_nmtuple(typename, fields)
else:
def NamedTuple(typename, fields):
"""Typed version of namedtuple.
Employee = collections.namedtuple('Employee', ['name', 'id'])

Usage::
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.) Alternative equivalent keyword syntax is also accepted::

Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
Employee = NamedTuple('Employee', name=str, id=int)

This is equivalent to::
In Python versions <= 3.5 use::

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.)
"""
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
_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)


Expand Down