Skip to content

Commit 56e6d65

Browse files
ilevkivskyigvanrossum
authored andcommitted
New backward-compatible NamedTuple (#282)
1 parent c9d303d commit 56e6d65

File tree

2 files changed

+70
-20
lines changed

2 files changed

+70
-20
lines changed

src/test_typing.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,10 @@ class CSub(B):
11691169
z: ClassVar['CSub'] = B()
11701170
class G(Generic[T]):
11711171
lst: ClassVar[List[T]] = []
1172+
1173+
class CoolEmployee(NamedTuple):
1174+
name: str
1175+
cool: int
11721176
"""
11731177

11741178
if PY36:
@@ -1586,6 +1590,17 @@ def test_basics(self):
15861590
self.assertEqual(Emp._fields, ('name', 'id'))
15871591
self.assertEqual(Emp._field_types, dict(name=str, id=int))
15881592

1593+
@skipUnless(PY36, 'Python 3.6 required')
1594+
def test_annotation_usage(self):
1595+
tim = CoolEmployee('Tim', 9000)
1596+
self.assertIsInstance(tim, CoolEmployee)
1597+
self.assertIsInstance(tim, tuple)
1598+
self.assertEqual(tim.name, 'Tim')
1599+
self.assertEqual(tim.cool, 9000)
1600+
self.assertEqual(CoolEmployee.__name__, 'CoolEmployee')
1601+
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
1602+
self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int))
1603+
15891604
def test_pickle(self):
15901605
global Emp # pickle wants to reference the class by name
15911606
Emp = NamedTuple('Emp', [('name', str), ('id', int)])

src/typing.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,31 +1801,66 @@ def new_user(user_class: Type[U]) -> U:
18011801
"""
18021802

18031803

1804-
def NamedTuple(typename, fields):
1805-
"""Typed version of namedtuple.
1804+
def _make_nmtuple(name, types):
1805+
nm_tpl = collections.namedtuple(name, [n for n, t in types])
1806+
nm_tpl._field_types = dict(types)
1807+
try:
1808+
nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__')
1809+
except (AttributeError, ValueError):
1810+
pass
1811+
return nm_tpl
18061812

1807-
Usage::
18081813

1809-
Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
1814+
if sys.version_info[:2] >= (3, 6):
1815+
class NamedTupleMeta(type):
18101816

1811-
This is equivalent to::
1817+
def __new__(cls, typename, bases, ns, *, _root=False):
1818+
if _root:
1819+
return super().__new__(cls, typename, bases, ns)
1820+
types = ns.get('__annotations__', {})
1821+
return _make_nmtuple(typename, types.items())
18121822

1813-
Employee = collections.namedtuple('Employee', ['name', 'id'])
1823+
class NamedTuple(metaclass=NamedTupleMeta, _root=True):
1824+
"""Typed version of namedtuple.
18141825
1815-
The resulting class has one extra attribute: _field_types,
1816-
giving a dict mapping field names to types. (The field names
1817-
are in the _fields attribute, which is part of the namedtuple
1818-
API.)
1819-
"""
1820-
fields = [(n, t) for n, t in fields]
1821-
cls = collections.namedtuple(typename, [n for n, t in fields])
1822-
cls._field_types = dict(fields)
1823-
# Set the module to the caller's module (otherwise it'd be 'typing').
1824-
try:
1825-
cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
1826-
except (AttributeError, ValueError):
1827-
pass
1828-
return cls
1826+
Usage::
1827+
1828+
class Employee(NamedTuple):
1829+
name: str
1830+
id: int
1831+
1832+
This is equivalent to::
1833+
1834+
Employee = collections.namedtuple('Employee', ['name', 'id'])
1835+
1836+
The resulting class has one extra attribute: _field_types,
1837+
giving a dict mapping field names to types. (The field names
1838+
are in the _fields attribute, which is part of the namedtuple
1839+
API.) Backward-compatible usage::
1840+
1841+
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
1842+
"""
1843+
1844+
def __new__(self, typename, fields):
1845+
return _make_nmtuple(typename, fields)
1846+
else:
1847+
def NamedTuple(typename, fields):
1848+
"""Typed version of namedtuple.
1849+
1850+
Usage::
1851+
1852+
Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)])
1853+
1854+
This is equivalent to::
1855+
1856+
Employee = collections.namedtuple('Employee', ['name', 'id'])
1857+
1858+
The resulting class has one extra attribute: _field_types,
1859+
giving a dict mapping field names to types. (The field names
1860+
are in the _fields attribute, which is part of the namedtuple
1861+
API.)
1862+
"""
1863+
return _make_nmtuple(typename, fields)
18291864

18301865

18311866
def NewType(name, tp):

0 commit comments

Comments
 (0)