Skip to content

Commit 5529359

Browse files
committed
Make NamedTuple provide __new__ instead of __init__
Closes #1279.
1 parent b06d60a commit 5529359

9 files changed

+50
-13
lines changed

mypy/checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,8 +1560,8 @@ def check_compatibility(self, name: str, base1: TypeInfo,
15601560
a direct subclass relationship (i.e., the compatibility requirement only derives from
15611561
multiple inheritance).
15621562
"""
1563-
if name == '__init__':
1564-
# __init__ can be incompatible -- it's a special case.
1563+
if name in ('__init__', '__new__', '__init_subclass__'):
1564+
# __init__ and friends can be incompatible -- it's a special case.
15651565
return
15661566
first = base1[name]
15671567
second = base2[name]

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ def analyze_class_body(self, defn: ClassDef) -> Iterator[bool]:
776776
nt_names = named_tuple_info.names
777777
named_tuple_info.names = SymbolTable()
778778
# This is needed for the cls argument to classmethods to get bound correctly.
779-
named_tuple_info.names['__init__'] = nt_names['__init__']
779+
named_tuple_info.names['__new__'] = nt_names['__new__']
780780

781781
self.enter_class(named_tuple_info)
782782

mypy/semanal_namedtuple.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,9 @@ def add_method(funcname: str,
347347
args: List[Argument],
348348
name: Optional[str] = None,
349349
is_classmethod: bool = False,
350+
is_new: bool = False,
350351
) -> None:
351-
if is_classmethod:
352+
if is_classmethod or is_new:
352353
first = [Argument(Var('cls'), TypeType.make_normalized(selftype), None, ARG_POS)]
353354
else:
354355
first = [Argument(Var('self'), selftype, None, ARG_POS)]
@@ -384,8 +385,9 @@ def make_init_arg(var: Var) -> Argument:
384385
kind = ARG_POS if default is None else ARG_OPT
385386
return Argument(var, var.type, default, kind)
386387

387-
add_method('__init__', ret=NoneTyp(), name=info.name(),
388-
args=[make_init_arg(var) for var in vars])
388+
add_method('__new__', ret=selftype, name=info.name(),
389+
args=[make_init_arg(var) for var in vars],
390+
is_new=True)
389391
add_method('_asdict', args=[], ret=ordereddictype)
390392
special_form_any = AnyType(TypeOfAny.special_form)
391393
add_method('_make', ret=selftype, is_classmethod=True,

test-data/unit/check-class-namedtuple.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ class XMethBad(NamedTuple):
587587
class MagicalFields(NamedTuple):
588588
x: int
589589
def __slots__(self) -> None: pass # E: Cannot overwrite NamedTuple attribute "__slots__"
590-
def __new__(cls) -> None: pass # E: Cannot overwrite NamedTuple attribute "__new__"
590+
def __new__(cls) -> None: pass # E: Name '__new__' already defined (possibly by an import)
591591
def _source(self) -> int: pass # E: Cannot overwrite NamedTuple attribute "_source"
592592
__annotations__ = {'x': float} # E: NamedTuple field name cannot start with an underscore: __annotations__ \
593593
# E: Invalid statement in NamedTuple definition; expected "field_name: field_type [= default]" \

test-data/unit/check-namedtuple.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,3 +732,15 @@ class CallableTuple(Thing):
732732

733733
o = CallableTuple('hello ', 12)
734734
o()
735+
736+
[case testNamedTupleNew]
737+
from typing import NamedTuple
738+
739+
Base = NamedTuple('Base', [('param', int)])
740+
741+
class Child(Base):
742+
def __new__(cls, param: int = 1) -> 'Child':
743+
return Base.__new__(cls, param)
744+
745+
Base(param=10)
746+
Child(param=10)

test-data/unit/cmdline.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ warn_unused_configs = True
11821182
[[mypy-emarg.hatch]
11831183
-- Currently we don't treat an unstructured pattern like a.*.b as unused
11841184
-- if it matches another section (like a.x.b). This would be reasonable
1185-
-- to change.
1185+
-- to change. '
11861186
[[mypy-a.*.b]
11871187
[[mypy-a.*.c]
11881188
[[mypy-a.x.b]
@@ -1257,6 +1257,6 @@ import d
12571257
[case testCacheMap]
12581258
-- This just checks that a valid --cache-map triple is accepted.
12591259
-- (Errors are too verbose to check.)
1260-
# cmd: mypy a.py --cache-map a.py a.meta.json a.data.json
1260+
# cmd: mypy a.py --cache-map a.py a.meta.json a.data.json
12611261
[file a.py]
12621262
[out]

test-data/unit/diff.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ M = NamedTuple('M', [('x', int), ('y', str)])
275275
[out]
276276
__main__.A
277277
__main__.N
278-
__main__.N.__init__
278+
__main__.N.__new__
279279
__main__.N._asdict
280280
__main__.N._make
281281
__main__.N._replace

test-data/unit/merge.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ TypeInfo<2>(
668668
Names(
669669
__annotations__<4> (builtins.object<1>)
670670
__doc__<5> (builtins.str<6>)
671-
__init__<7>
671+
__new__<7>
672672
_asdict<8>
673673
_field_defaults<9> (builtins.object<1>)
674674
_field_types<10> (builtins.object<1>)
@@ -690,7 +690,7 @@ TypeInfo<2>(
690690
Names(
691691
__annotations__<4> (builtins.object<1>)
692692
__doc__<5> (builtins.str<6>)
693-
__init__<7>
693+
__new__<7>
694694
_asdict<8>
695695
_field_defaults<9> (builtins.object<1>)
696696
_field_types<10> (builtins.object<1>)

test-data/unit/pythoneval.test

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ print(list(reversed(A())))
2626
[[3, 2, 1]
2727
[['c', 'b', 'a']
2828
[['f', 'o', 'o']
29+
-- ]]]]]
2930

3031
[case testIntAndFloatConversion]
3132
from typing import SupportsInt, SupportsFloat
@@ -90,6 +91,7 @@ import typing
9091
print(list.__add__([1, 2], [3, 4]))
9192
[out]
9293
[[1, 2, 3, 4]
94+
-- ]
9395

9496
[case testInheritedClassAttribute]
9597
import typing
@@ -1047,7 +1049,7 @@ _testTypedDictGet.py:9: error: TypedDict "D" has no key 'z'
10471049
_testTypedDictGet.py:10: error: All overload variants of "get" of "Mapping" require at least one argument
10481050
_testTypedDictGet.py:10: note: Possible overload variants:
10491051
_testTypedDictGet.py:10: note: def get(self, k: str) -> object
1050-
_testTypedDictGet.py:10: note: def [_T] get(self, k: str, default: object) -> object
1052+
_testTypedDictGet.py:10: note: def [_T] get(self, k: str, default: object) -> object
10511053
_testTypedDictGet.py:12: error: Revealed type is 'builtins.object*'
10521054

10531055
[case testTypedDictMappingMethods]
@@ -1317,3 +1319,24 @@ def g(ms: 'T[M]') -> None:
13171319
reduce(f, ms)
13181320
T = Iterable
13191321
[out]
1322+
1323+
[case testNamedTupleNew]
1324+
# This is an eval test because there was a snag found only with full stubs
1325+
from typing import NamedTuple
1326+
1327+
Base = NamedTuple('Base', [('param', int)])
1328+
1329+
class Child(Base):
1330+
def __new__(cls, param: int = 1) -> 'Child':
1331+
return Base.__new__(cls, param)
1332+
1333+
Base(param=10)
1334+
Child(param=10)
1335+
reveal_type(Child())
1336+
1337+
from collections import namedtuple
1338+
X = namedtuple('X', ['a', 'b'])
1339+
x = X(a=1, b='s')
1340+
1341+
[out]
1342+
_testNamedTupleNew.py:12: error: Revealed type is 'Tuple[builtins.int, fallback=_testNamedTupleNew.Child]'

0 commit comments

Comments
 (0)