Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 4d5097a

Browse files
committed
Issue python#28556: Allow defining methods in NamedTuple class syntax (python#362) (3.6->3.7)
2 parents 62f82a9 + 744cd63 commit 4d5097a

File tree

2 files changed

+31
-5
lines changed

2 files changed

+31
-5
lines changed

Lib/test/test_typing.py

+27-5
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,10 @@ def test_new_repr_complex(self):
612612
self.assertEqual(repr(typing.Mapping[T, TS][TS, T]), 'typing.Mapping[~TS, ~T]')
613613
self.assertEqual(repr(List[Tuple[T, TS]][int, T]),
614614
'typing.List[typing.Tuple[int, ~T]]')
615-
self.assertEqual(repr(List[Tuple[T, T]][List[int]]),
616-
'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]')
615+
self.assertEqual(
616+
repr(List[Tuple[T, T]][List[int]]),
617+
'typing.List[typing.Tuple[typing.List[int], typing.List[int]]]'
618+
)
617619

618620
def test_new_repr_bare(self):
619621
T = TypeVar('T')
@@ -684,8 +686,10 @@ def naive_dict_check(obj, tp):
684686
raise NotImplementedError
685687
if tp.__args__:
686688
KT, VT = tp.__args__
687-
return all(isinstance(k, KT) and isinstance(v, VT)
688-
for k, v in obj.items())
689+
return all(
690+
isinstance(k, KT) and isinstance(v, VT)
691+
for k, v in obj.items()
692+
)
689693
self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[str, int]))
690694
self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[str, int]))
691695
with self.assertRaises(NotImplementedError):
@@ -1409,6 +1413,16 @@ class CoolEmployee(NamedTuple):
14091413
class CoolEmployeeWithDefault(NamedTuple):
14101414
name: str
14111415
cool: int = 0
1416+
1417+
class XMeth(NamedTuple):
1418+
x: int
1419+
def double(self):
1420+
return 2 * self.x
1421+
1422+
class XMethBad(NamedTuple):
1423+
x: int
1424+
def _fields(self):
1425+
return 'no chance for this'
14121426
"""
14131427

14141428
if PY36:
@@ -1417,6 +1431,7 @@ class CoolEmployeeWithDefault(NamedTuple):
14171431
# fake names for the sake of static analysis
14181432
ann_module = ann_module2 = ann_module3 = None
14191433
A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = object
1434+
XMeth = XMethBad = object
14201435

14211436
gth = get_type_hints
14221437

@@ -1750,7 +1765,7 @@ def test_no_generator_instantiation(self):
17501765
def test_async_generator(self):
17511766
ns = {}
17521767
exec("async def f():\n"
1753-
" yield 42\n", globals(), ns)
1768+
" yield 42\n", globals(), ns)
17541769
g = ns['f']()
17551770
self.assertIsSubclass(type(g), typing.AsyncGenerator)
17561771

@@ -2038,6 +2053,13 @@ class NonDefaultAfterDefault(NamedTuple):
20382053
y: int
20392054
""")
20402055

2056+
@skipUnless(PY36, 'Python 3.6 required')
2057+
def test_annotation_usage_with_methods(self):
2058+
self.assertEquals(XMeth(1).double(), 2)
2059+
self.assertEquals(XMeth(42).x, XMeth(42)[0])
2060+
self.assertEquals(XMethBad(1)._fields, ('x',))
2061+
self.assertEquals(XMethBad(1).__annotations__, {'x': int})
2062+
20412063
@skipUnless(PY36, 'Python 3.6 required')
20422064
def test_namedtuple_keyword_usage(self):
20432065
LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)

Lib/typing.py

+4
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,10 @@ def __new__(cls, typename, bases, ns):
20002000
default_names=', '.join(defaults_dict.keys())))
20012001
nm_tpl.__new__.__defaults__ = tuple(defaults)
20022002
nm_tpl._field_defaults = defaults_dict
2003+
# update from user namespace without overriding special namedtuple attributes
2004+
for key in ns:
2005+
if not hasattr(nm_tpl, key):
2006+
setattr(nm_tpl, key, ns[key])
20032007
return nm_tpl
20042008

20052009

0 commit comments

Comments
 (0)