summaryrefslogtreecommitdiff
path: root/Lib
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2017-01-18 08:03:50 -0800
committerGuido van Rossum <guido@python.org>2017-01-18 08:03:50 -0800
commit0064915279521dac3c873de7cdc30bdb5db70094 (patch)
treee073cf3c8da480f7fcd91e96fb62c38de76c3e36 /Lib
parent500f3d1a3081681b2042a2b2d33eb5fa62cd42ba (diff)
downloadcpython-0064915279521dac3c873de7cdc30bdb5db70094.tar.gz
Issue #28556: allow default values in class form of NamedTuple -- Jelle Zijlstra
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_typing.py26
-rw-r--r--Lib/typing.py17
2 files changed, 42 insertions, 1 deletions
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:
@@ -1960,6 +1964,28 @@ class NamedTupleTests(BaseTestCase):
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)
nick = LocalEmployee('Nick', 25)
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.