From 3c268be885d62b1b5ac572340641024af2c02ce4 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 18 Jan 2017 08:03:50 -0800 Subject: [PATCH] Issue #28556: allow default values in class form of NamedTuple -- Jelle Zijlstra --- Lib/test/test_typing.py | 26 ++++++++++++++++++++++++++ Lib/typing.py | 17 ++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 73bcda1e01..44712b699b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1400,6 +1400,10 @@ class G(Generic[T]): class CoolEmployee(NamedTuple): name: str cool: int + +class CoolEmployeeWithDefault(NamedTuple): + name: str + cool: int = 0 """ if PY36: @@ -1959,6 +1963,28 @@ class NamedTupleTests(BaseTestCase): collections.OrderedDict(name=str, cool=int)) self.assertIs(CoolEmployee._field_types, CoolEmployee.__annotations__) + @skipUnless(PY36, 'Python 3.6 required') + def test_annotation_usage_with_default(self): + jelle = CoolEmployeeWithDefault('Jelle') + self.assertIsInstance(jelle, CoolEmployeeWithDefault) + self.assertIsInstance(jelle, tuple) + self.assertEqual(jelle.name, 'Jelle') + self.assertEqual(jelle.cool, 0) + cooler_employee = CoolEmployeeWithDefault('Sjoerd', 1) + self.assertEqual(cooler_employee.cool, 1) + + self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault') + self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool')) + self.assertEqual(CoolEmployeeWithDefault._field_types, dict(name=str, cool=int)) + self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) + + with self.assertRaises(TypeError): + exec(""" +class NonDefaultAfterDefault(NamedTuple): + x: int = 3 + y: int +""") + @skipUnless(PY36, 'Python 3.6 required') def test_namedtuple_keyword_usage(self): LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) diff --git a/Lib/typing.py b/Lib/typing.py index b798830f65..0aeb089d5d 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1959,7 +1959,22 @@ class NamedTupleMeta(type): raise TypeError("Class syntax for NamedTuple is only supported" " in Python 3.6+") types = ns.get('__annotations__', {}) - return _make_nmtuple(typename, types.items()) + nm_tpl = _make_nmtuple(typename, types.items()) + defaults = [] + defaults_dict = {} + for field_name in types: + if field_name in ns: + default_value = ns[field_name] + defaults.append(default_value) + defaults_dict[field_name] = default_value + elif defaults: + raise TypeError("Non-default namedtuple field {field_name} cannot follow default" + " field(s) {default_names}" + .format(field_name=field_name, + default_names=', '.join(defaults_dict.keys()))) + nm_tpl.__new__.__defaults__ = tuple(defaults) + nm_tpl._field_defaults = defaults_dict + return nm_tpl class NamedTuple(metaclass=NamedTupleMeta): """Typed version of namedtuple. -- 2.40.0