summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2016-11-15 17:48:06 (GMT)
committerGuido van Rossum <guido@python.org>2016-11-15 17:48:06 (GMT)
commit2f841442354f41f2490cef76e9ba24bfef9dfbea (patch)
tree9b1aeee58baadad475ce4f69cc782e3a237224f5 /Lib
parent49e8f2d2040d3c15d61a713dcbe14caae41c4f0d (diff)
downloadcpython-2f841442354f41f2490cef76e9ba24bfef9dfbea.zip
cpython-2f841442354f41f2490cef76e9ba24bfef9dfbea.tar.gz
cpython-2f841442354f41f2490cef76e9ba24bfef9dfbea.tar.bz2
Issue #28556: Allow keyword syntax for NamedTuple (Ivan Levkivskyi) (upstream #321)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_typing.py14
-rw-r--r--Lib/typing.py76
2 files changed, 53 insertions, 37 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 0582845..12bbf16 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -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)])
diff --git a/Lib/typing.py b/Lib/typing.py
index 7a64e07..fe22b2b 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -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,55 +1886,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)