Skip to content

Commit 33560c8

Browse files
authored
Allow overriding some methods in NamedTuple (#370)
* Allow owerriding some methods in NamededTuple * Fix lint * Response to review comments (performance)
1 parent 2d85d8c commit 33560c8

File tree

2 files changed

+22
-7
lines changed

2 files changed

+22
-7
lines changed

src/test_typing.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1484,14 +1484,23 @@ class XMeth(NamedTuple):
14841484
x: int
14851485
def double(self):
14861486
return 2 * self.x
1487+
1488+
class XRepr(NamedTuple):
1489+
x: int
1490+
y: int = 1
1491+
def __str__(self):
1492+
return f'{self.x} -> {self.y}'
1493+
def __add__(self, other):
1494+
return 0
14871495
"""
14881496

14891497
if PY36:
14901498
exec(PY36_TESTS)
14911499
else:
14921500
# fake names for the sake of static analysis
14931501
ann_module = ann_module2 = ann_module3 = None
1494-
A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = XMeth = object
1502+
A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = object
1503+
XMeth = XRepr = object
14951504

14961505
gth = get_type_hints
14971506

@@ -2128,6 +2137,8 @@ class NonDefaultAfterDefault(NamedTuple):
21282137
def test_annotation_usage_with_methods(self):
21292138
self.assertEqual(XMeth(1).double(), 2)
21302139
self.assertEqual(XMeth(42).x, XMeth(42)[0])
2140+
self.assertEqual(str(XRepr(42)), '42 -> 1')
2141+
self.assertEqual(XRepr(1, 2) + XRepr(3), 0)
21312142

21322143
with self.assertRaises(AttributeError):
21332144
exec("""

src/typing.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,13 @@ def _make_nmtuple(name, types):
19821982

19831983
_PY36 = sys.version_info[:2] >= (3, 6)
19841984

1985+
# attributes prohibited to set in NamedTuple class syntax
1986+
_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__',
1987+
'_fields', '_field_defaults', '_field_types',
1988+
'_make', '_replace', '_asdict')
1989+
1990+
_special = ('__module__', '__name__', '__qualname__', '__annotations__')
1991+
19851992

19861993
class NamedTupleMeta(type):
19871994

@@ -2009,13 +2016,10 @@ def __new__(cls, typename, bases, ns):
20092016
nm_tpl._field_defaults = defaults_dict
20102017
# update from user namespace without overriding special namedtuple attributes
20112018
for key in ns:
2012-
if not hasattr(nm_tpl, key):
2013-
setattr(nm_tpl, key, ns[key])
2014-
elif (
2015-
key not in ['__module__', '__qualname__', '__annotations__'] and
2016-
key not in nm_tpl._field_defaults
2017-
):
2019+
if key in _prohibited:
20182020
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
2021+
elif key not in _special and key not in nm_tpl._fields:
2022+
setattr(nm_tpl, key, ns[key])
20192023
return nm_tpl
20202024

20212025

0 commit comments

Comments
 (0)