Issue #28556: allow default values in class form of NamedTuple -- Jelle Zijlstra

This commit is contained in:
Guido van Rossum 2017-01-18 08:03:50 -08:00
parent 37f183d43d
commit 3c268be885
2 changed files with 42 additions and 1 deletions

View File

@ -1400,6 +1400,10 @@ class G(Generic[T]):
class CoolEmployee(NamedTuple): class CoolEmployee(NamedTuple):
name: str name: str
cool: int cool: int
class CoolEmployeeWithDefault(NamedTuple):
name: str
cool: int = 0
""" """
if PY36: if PY36:
@ -1959,6 +1963,28 @@ class NamedTupleTests(BaseTestCase):
collections.OrderedDict(name=str, cool=int)) collections.OrderedDict(name=str, cool=int))
self.assertIs(CoolEmployee._field_types, CoolEmployee.__annotations__) 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') @skipUnless(PY36, 'Python 3.6 required')
def test_namedtuple_keyword_usage(self): def test_namedtuple_keyword_usage(self):
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)

View File

@ -1959,7 +1959,22 @@ class NamedTupleMeta(type):
raise TypeError("Class syntax for NamedTuple is only supported" raise TypeError("Class syntax for NamedTuple is only supported"
" in Python 3.6+") " in Python 3.6+")
types = ns.get('__annotations__', {}) 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): class NamedTuple(metaclass=NamedTupleMeta):
"""Typed version of namedtuple. """Typed version of namedtuple.