Skip to content

Commit 2f84144

Browse files
committed
Issue #28556: Allow keyword syntax for NamedTuple (Ivan Levkivskyi) (upstream #321)
1 parent 49e8f2d commit 2f84144

File tree

2 files changed

+53
-37
lines changed

2 files changed

+53
-37
lines changed

Lib/test/test_typing.py

+14
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,20 @@ def test_annotation_usage(self):
18651865
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
18661866
self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int))
18671867

1868+
@skipUnless(PY36, 'Python 3.6 required')
1869+
def test_namedtuple_keyword_usage(self):
1870+
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
1871+
nick = LocalEmployee('Nick', 25)
1872+
self.assertIsInstance(nick, tuple)
1873+
self.assertEqual(nick.name, 'Nick')
1874+
self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
1875+
self.assertEqual(LocalEmployee._fields, ('name', 'age'))
1876+
self.assertEqual(LocalEmployee._field_types, dict(name=str, age=int))
1877+
with self.assertRaises(TypeError):
1878+
NamedTuple('Name', [('x', int)], y=str)
1879+
with self.assertRaises(TypeError):
1880+
NamedTuple('Name', x=1, y='a')
1881+
18681882
def test_pickle(self):
18691883
global Emp # pickle wants to reference the class by name
18701884
Emp = NamedTuple('Emp', [('name', str), ('id', int)])

Lib/typing.py

+39-37
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,8 @@ def new_user(user_class: Type[U]) -> U:
18751875

18761876

18771877
def _make_nmtuple(name, types):
1878+
msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type"
1879+
types = [(n, _type_check(t, msg)) for n, t in types]
18781880
nm_tpl = collections.namedtuple(name, [n for n, t in types])
18791881
nm_tpl._field_types = dict(types)
18801882
try:
@@ -1884,55 +1886,55 @@ def _make_nmtuple(name, types):
18841886
return nm_tpl
18851887

18861888

1887-
if sys.version_info[:2] >= (3, 6):
1888-
class NamedTupleMeta(type):
1889+
_PY36 = sys.version_info[:2] >= (3, 6)
18891890

1890-
def __new__(cls, typename, bases, ns, *, _root=False):
1891-
if _root:
1892-
return super().__new__(cls, typename, bases, ns)
1893-
types = ns.get('__annotations__', {})
1894-
return _make_nmtuple(typename, types.items())
18951891

1896-
class NamedTuple(metaclass=NamedTupleMeta, _root=True):
1897-
"""Typed version of namedtuple.
1892+
class NamedTupleMeta(type):
18981893

1899-
Usage::
1894+
def __new__(cls, typename, bases, ns):
1895+
if ns.get('_root', False):
1896+
return super().__new__(cls, typename, bases, ns)
1897+
if not _PY36:
1898+
raise TypeError("Class syntax for NamedTuple is only supported"
1899+
" in Python 3.6+")
1900+
types = ns.get('__annotations__', {})
1901+
return _make_nmtuple(typename, types.items())
19001902

1901-
class Employee(NamedTuple):
1902-
name: str
1903-
id: int
1903+
class NamedTuple(metaclass=NamedTupleMeta):
1904+
"""Typed version of namedtuple.
19041905
1905-
This is equivalent to::
1906+
Usage in Python versions >= 3.6::
19061907
1907-
Employee = collections.namedtuple('Employee', ['name', 'id'])
1908+
class Employee(NamedTuple):
1909+
name: str
1910+
id: int
19081911
1909-
The resulting class has one extra attribute: _field_types,
1910-
giving a dict mapping field names to types. (The field names
1911-
are in the _fields attribute, which is part of the namedtuple
1912-
API.) Backward-compatible usage::
1912+
This is equivalent to::
19131913
1914-
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
1915-
"""
1916-
1917-
def __new__(self, typename, fields):
1918-
return _make_nmtuple(typename, fields)
1919-
else:
1920-
def NamedTuple(typename, fields):
1921-
"""Typed version of namedtuple.
1914+
Employee = collections.namedtuple('Employee', ['name', 'id'])
19221915
1923-
Usage::
1916+
The resulting class has one extra attribute: _field_types,
1917+
giving a dict mapping field names to types. (The field names
1918+
are in the _fields attribute, which is part of the namedtuple
1919+
API.) Alternative equivalent keyword syntax is also accepted::
19241920
1925-
Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
1921+
Employee = NamedTuple('Employee', name=str, id=int)
19261922
1927-
This is equivalent to::
1923+
In Python versions <= 3.5 use::
19281924
1929-
Employee = collections.namedtuple('Employee', ['name', 'id'])
1930-
1931-
The resulting class has one extra attribute: _field_types,
1932-
giving a dict mapping field names to types. (The field names
1933-
are in the _fields attribute, which is part of the namedtuple
1934-
API.)
1935-
"""
1925+
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
1926+
"""
1927+
_root = True
1928+
1929+
def __new__(self, typename, fields=None, **kwargs):
1930+
if kwargs and not _PY36:
1931+
raise TypeError("Keyword syntax for NamedTuple is only supported"
1932+
" in Python 3.6+")
1933+
if fields is None:
1934+
fields = kwargs.items()
1935+
elif kwargs:
1936+
raise TypeError("Either list of fields or keywords"
1937+
" can be provided to NamedTuple, not both")
19361938
return _make_nmtuple(typename, fields)
19371939

19381940

0 commit comments

Comments
 (0)