(original) (raw)
changeset: 105129:38ec88a4e282 branch: 3.6 parent: 105126:3c6e5f83d235 parent: 105128:da2ac103d326 user: Guido van Rossum guido@python.org date: Tue Nov 15 09:48:09 2016 -0800 files: Lib/test/test_typing.py Lib/typing.py description: Issue #28556: Allow keyword syntax for NamedTuple (Ivan Levkivskyi) (upstream #321) (3.5->3.6) diff -r 3c6e5f83d235 -r 38ec88a4e282 Lib/test/test_typing.py --- a/Lib/test/test_typing.py Tue Nov 15 17:24:42 2016 +0100 +++ b/Lib/test/test_typing.py Tue Nov 15 09:48:09 2016 -0800 @@ -1865,6 +1865,20 @@ 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 -r 3c6e5f83d235 -r 38ec88a4e282 Lib/typing.py --- a/Lib/typing.py Tue Nov 15 17:24:42 2016 +0100 +++ b/Lib/typing.py Tue Nov 15 09:48:09 2016 -0800 @@ -1875,6 +1875,8 @@ 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 @@ return nm_tpl -if sys.version_info[:2] >= (3, 6): - class NamedTupleMeta(type): - - 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. - - Usage:: - - class Employee(NamedTuple): - 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.) Backward-compatible usage:: - - 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. - - 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.) - """ +_PY36 = sys.version_info[:2] >= (3, 6) + + +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): + """Typed version of namedtuple. + + Usage in Python versions >= 3.6:: + + class Employee(NamedTuple): + 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.) 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=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) /guido@python.org