Skip to content

namedtuple: add defaults to the Python 3.6 syntax #338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,10 @@ class G(Generic[T]):
class CoolEmployee(NamedTuple):
name: str
cool: int

class CoolEmployeeWithDefault(NamedTuple):
name: str
cool: int = 0
"""

if PY36:
Expand Down Expand Up @@ -1920,6 +1924,28 @@ def test_annotation_usage(self):
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int))

@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)
Expand Down
17 changes: 16 additions & 1 deletion src/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1916,7 +1916,22 @@ def __new__(cls, typename, bases, ns):
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also probably you could use only defaults_dict and then write here = tuple(defaults_dict.values()), since dict is ordered in Python 3.6.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, dict ordering is an implementation detail of CPython 3.6 and a conforming Python 3.6 implementation could choose to not implement ordering, so this code shouldn't rely on dict ordering (except for class namespaces and **kwargs because of PEP 520 and PEP 468).

nm_tpl._field_defaults = defaults_dict
return nm_tpl

class NamedTuple(metaclass=NamedTupleMeta):
"""Typed version of namedtuple.
Expand Down