crates/ty_python_semantic/resources/mdtest/call/constructor.md
When classes are instantiated, Python calls the metaclass's __call__ method. The metaclass of most
Python classes is the class builtins.type.
type.__call__ calls the __new__ method of the class, which is responsible for creating the
instance. __init__ is then called on the constructed instance with the same arguments that were
passed to __new__.
Both __new__ and __init__ are looked up using the descriptor protocol, i.e., __get__ is called
if these attributes are descriptors. __new__ is always treated as a static method, i.e., cls is
passed as the first argument. __init__ has no special handling; it is fetched as a bound method
and called just like any other dunder method.
type.__call__ does other things too, but this is not yet handled by us.
Since every class has object in its MRO, the default implementations are object.__new__ and
object.__init__. They have some special behavior, namely:
__new__ nor __init__ are defined anywhere in the MRO of class (except for
object), no arguments are accepted and TypeError is raised if any are passed.__new__ is defined but __init__ is not, object.__init__ will allow arbitrary arguments!object class itselfTest the behavior of the object class itself. As implementation has to ignore object own methods
as defined in typeshed due to behavior not expressible in typeshed (see above how __init__ behaves
differently depending on whether __new__ is defined or not), we have to test the behavior of
object itself.
reveal_type(object()) # revealed: object
# error: [too-many-positional-arguments] "Too many positional arguments to class `object`: expected 0, got 1"
reveal_type(object(1)) # revealed: object
class Foo: ...
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to `object.__init__`: expected 1, got 2"
reveal_type(Foo(1)) # revealed: Foo
__new__ present on the class itselfclass Foo:
def __new__(cls, x: int) -> "Foo":
return object.__new__(cls)
reveal_type(Foo(1)) # revealed: Foo
# error: [invalid-argument-type] "Argument to constructor `Foo.__new__` is incorrect: Expected `int`, found `Literal["x"]`"
reveal_type(Foo("x")) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of constructor `Foo.__new__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to constructor `Foo.__new__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
__new__ present on a superclassIf the __new__ method is defined on a superclass, we can still infer the signature of the
constructor from it.
from typing_extensions import Self
class Base:
def __new__(cls, x: int) -> Self:
return cls(x)
class Foo(Base): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of constructor `Base.__new__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to constructor `Base.__new__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
__new__ present but __init__ missingobject.__init__ allows arbitrary arguments when a custom __new__ exists. This should not trigger
__init__ argument errors.
class Foo:
def __new__(cls, x: int):
return object.__new__(cls)
reveal_type(Foo(1)) # revealed: Foo
Foo(1)
# error: [too-many-positional-arguments] "Too many positional arguments to constructor `Foo.__new__`: expected 2, got 3"
Foo(1, 2)
__new__def _(flag: bool) -> None:
class Foo:
if flag:
def __new__(cls, x: int): ...
else:
def __new__(cls, x: int, y: int = 1): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [invalid-argument-type] "Argument to constructor `Foo.__new__` is incorrect: Expected `int`, found `Literal["1"]`"
# error: [invalid-argument-type] "Argument to constructor `Foo.__new__` is incorrect: Expected `int`, found `Literal["1"]`"
reveal_type(Foo("1")) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of constructor `Foo.__new__`"
# error: [missing-argument] "No argument provided for required parameter `x` of constructor `Foo.__new__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to constructor `Foo.__new__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
__new__class SomeCallable:
def __call__(self, cls, x: int) -> "Foo":
obj = object.__new__(cls)
obj.x = x
return obj
class Descriptor:
def __get__(self, instance, owner) -> SomeCallable:
return SomeCallable()
class Foo:
__new__: Descriptor = Descriptor()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `SomeCallable.__call__`"
reveal_type(Foo()) # revealed: Foo
__new__ is implicitly a static method, but explicitly marking it as one is harmlessclass Foo:
@staticmethod
def __new__(cls, x: int):
return object.__new__(cls)
reveal_type(Foo(1)) # revealed: Foo
__new__ defined as a classmethodMarking it as a classmethod, on the other hand, breaks at runtime.
class Foo:
@classmethod
def __new__(cls, x: int):
return object.__new__(cls)
# error: [invalid-argument-type] "Argument to bound method `Foo.__new__` is incorrect: Expected `int`, found `<class 'Foo'>`"
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `Foo.__new__`: expected 1, got 2"
Foo(1)
__new__class Callable:
def __call__(self, cls, x: int) -> "Foo":
return object.__new__(cls)
class Foo:
__new__ = Callable()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `Callable.__call__`"
reveal_type(Foo()) # revealed: Foo
__new__ methoddef _(flag: bool) -> None:
class Foo:
if flag:
def __new__(cls):
return object.__new__(cls)
# error: [possibly-missing-implicit-call]
reveal_type(Foo()) # revealed: Foo
# error: [possibly-missing-implicit-call]
# error: [too-many-positional-arguments]
reveal_type(Foo(1)) # revealed: Foo
__call__ on __new__ callabledef _(flag: bool) -> None:
class Callable:
if flag:
def __call__(self, cls, x: int) -> "Foo":
return object.__new__(cls)
class Foo:
__new__ = Callable()
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
reveal_type(Foo(1)) # revealed: Foo
# TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
# but we currently infer the signature of `__call__` as unknown, so it accepts any arguments
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
reveal_type(Foo()) # revealed: Foo
__init__ present on the class itselfIf the class has an __init__ method, we can infer the signature of the constructor from it.
class Foo:
def __init__(self, x: int): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of `Foo.__init__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to `Foo.__init__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
__new__ return typePython's __new__ method can return any type, not just an instance of the class. When __new__
returns a type that is not a subtype of the class instance type, we use the returned type directly,
without checking __init__.
__new__ returning a different typeclass ReturnsInt:
def __new__(cls) -> int:
return 42
reveal_type(ReturnsInt()) # revealed: int
x: int = ReturnsInt() # OK
y: ReturnsInt = ReturnsInt() # error: [invalid-assignment]
In this case, we don't validate __init__:
class ReturnsIntWithInit:
def __new__(cls) -> int:
return 42
def __init__(self, x: str) -> None: ...
# No error from missing argument to `__init__`:
reveal_type(ReturnsIntWithInit()) # revealed: int
__new__ returning a union typeclass MaybeInt:
def __new__(cls, value: str) -> "int | MaybeInt":
try:
return int(value)
except ValueError:
return object.__new__(cls)
reveal_type(MaybeInt("42")) # revealed: int | MaybeInt
a: int | MaybeInt = MaybeInt("42") # OK
b: int = MaybeInt("42") # error: [invalid-assignment]
__new__ returning an intersection typefrom __future__ import annotations
from ty_extensions import Intersection
class Mixin:
pass
class A:
def __new__(cls) -> Intersection[A, Mixin]:
raise NotImplementedError()
def __init__(self, x: int) -> None: ...
# error: [missing-argument]
reveal_type(A()) # revealed: A & Mixin
__new__ returning the class typeWhen __new__ returns the type of the instance being constructed, we use that type:
class Normal:
def __new__(cls) -> "Normal":
return object.__new__(cls)
reveal_type(Normal()) # revealed: Normal
And we do validate __init__:
class NormalWithInit:
def __new__(cls) -> "NormalWithInit":
return object.__new__(cls)
def __init__(self, x: int) -> None: ...
# error: [missing-argument]
reveal_type(NormalWithInit()) # revealed: NormalWithInit
__new__ with no return type annotationWhen __new__ has no return type annotation, we fall back to the instance type.
class NoAnnotation:
def __new__(cls):
return object.__new__(cls)
reveal_type(NoAnnotation()) # revealed: NoAnnotation
__new__ returning AnyPer the spec, "an explicit return type of Any should be treated as a type that is not an instance
of the class being constructed." This means __init__ is not called and the return type is Any.
from typing import Any
class ReturnsAny:
def __new__(cls) -> Any:
return 42
def __init__(self, x: int) -> None:
pass
# __init__ is skipped because `-> Any` is treated as non-instance per spec
reveal_type(ReturnsAny()) # revealed: Any
__new__ returning NeverWhen __new__ returns Never, the call is terminal and __init__ is skipped.
from typing_extensions import Never
class NewNeverReturns:
def __new__(cls) -> Never:
raise NotImplementedError
def __init__(self, x: int) -> None:
pass
# `__init__` is skipped because `__new__` never returns.
reveal_type(NewNeverReturns()) # revealed: Never
__new__ returning a union containing AnyWhen __new__ returns a union containing Any, since we don't consider Any a subtype of the
instance type, __init__ is skipped.
from typing import Any
class MaybeAny:
def __new__(cls, value: int) -> "MaybeAny | Any":
if value > 0:
return object.__new__(cls)
return None
def __init__(self) -> None:
pass
reveal_type(MaybeAny(1)) # revealed: MaybeAny | Any
__new__ returning a non-self typevarWhen __new__ returns a type variable that is not Self, we should specialize it before
categorizing the return type as instance or non-instance.
from typing import Generic, TypeVar
T = TypeVar("T")
class C(Generic[T]):
def __new__(cls, x: T) -> T:
return x
def __init__(self) -> None: ...
# `Literal[1]` is not an instance of `C`, so `__init__` is skipped.
reveal_type(C(1)) # revealed: Literal[1]
def _(c: C[str]):
# `C[str]` is an instance of `C`, so `__init__` is checked and fails.
# error: [too-many-positional-arguments]
reveal_type(C(c)) # revealed: C[str]
__new__ typevars should still provide __init__ type contextWhen __new__ returns the constructed type via a cls: type[T] -> T annotation, we should still
use __init__ to provide argument type context for constructor arguments.
Any-typed __new__ parameter should not block __init__ type contextfrom __future__ import annotations
from typing import Any, Callable, TypeVar
T = TypeVar("T", bound="SpanData")
class SpanData:
def __new__(
cls: type[T],
name: str,
on_finish: Any | None = None,
) -> T:
return object.__new__(cls)
class Span(SpanData):
def __init__(self, name: str, on_finish: list[Callable[[Span], None]] | None = None) -> None:
pass
class Tracer:
def _on_span_finish(self, span: Span) -> None:
pass
def start(self) -> None:
Span("x", on_finish=[self._on_span_finish])
object-typed __new__ parameter should not block __init__ type contextfrom typing import Callable, TypeVar
T = TypeVar("T", bound="SpanData")
class SpanData:
def __new__(
cls: type[T],
name: str,
on_finish: object | None = None,
) -> T:
return object.__new__(cls)
class Span(SpanData):
def __init__(self, name: str, on_finish: list[Callable[["Span"], None]] | None = None) -> None:
pass
class Tracer:
def _on_span_finish(self, span: "Span") -> None:
pass
def start(self) -> None:
Span("x", on_finish=[self._on_span_finish])
cls: type[T] -> T should still allow literal promotion for invariant class type parametersfrom typing import Generic, TypeVar
S = TypeVar("S")
T = TypeVar("T", bound="Box")
class Box(Generic[S]):
def __new__(cls: type[T], x: S) -> T:
return super().__new__(cls)
reveal_type(Box(42)) # revealed: Box[int]
typing.Self return should still provide __init__ type context[environment]
python-version = "3.12"
from __future__ import annotations
from typing import Callable, Self, Any
class SpanData:
def __new__(
cls,
name: str,
on_finish: Any | None = None,
) -> Self:
return object.__new__(cls)
class Span(SpanData):
def __init__(self, name: str, on_finish: list[Callable[[Span], None]] | None = None) -> None:
pass
class Tracer:
def _on_span_finish(self, span: Span) -> None:
pass
def start(self) -> None:
Span("x", on_finish=[self._on_span_finish])
__new__ returning a specific class affects subclassesWhen __new__ returns a specific class (e.g., -> Foo), this is an instance type for Foo itself,
so __init__ is checked. But for a subclass Bar(Foo), the return type Foo is NOT an instance of
Bar, so the __new__ return type is used directly and Bar.__init__ is skipped.
class Foo:
def __new__(cls, x: int = 0) -> "Foo":
return object.__new__(cls)
def __init__(self, x: int) -> None:
pass
class Bar(Foo):
def __init__(self, y: str) -> None:
pass
# For Foo: return type `Foo` IS an instance of `Foo`, so `__init__` is checked.
Foo() # error: [missing-argument]
reveal_type(Foo(1)) # revealed: Foo
# For Bar: return type `Foo` is NOT an instance of `Bar`, so `__init__` is
# skipped and `Foo` is used directly.
reveal_type(Bar()) # revealed: Foo
reveal_type(Bar(1)) # revealed: Foo
__new__ can remap an explicit generic specializationfrom typing import Generic, TypeVar
T = TypeVar("T")
class Class8(Generic[T]):
def __new__(cls, *args, **kwargs) -> "Class8[list[T]]":
raise NotImplementedError
reveal_type(Class8[int]()) # revealed: Class8[list[int]]
reveal_type(Class8[str]()) # revealed: Class8[list[str]]
__new__ returning Self preserves explicit specializationfrom typing import Generic, TypeVar
from typing_extensions import Self
T = TypeVar("T")
class Class9(Generic[T]):
def __new__(cls, x: T) -> Self:
return super().__new__(cls)
reveal_type(Class9[int](1)) # revealed: Class9[int]
__new__ can fix generic specialization and still validate __init__[environment]
python-version = "3.12"
class C[T]:
def __new__(cls) -> "C[int]":
raise NotImplementedError()
def __init__(self, x: int) -> None:
pass
# error: [missing-argument]
reveal_type(C()) # revealed: C[int]
# error: [missing-argument]
reveal_type(C[str]()) # revealed: C[int]
# error: [missing-argument]
reveal_type(C[int]()) # revealed: C[int]
__new__ with method-level type variables mapping to class specializationWhen __new__ has its own type parameters that map to the class's type parameter through the return
type, we should correctly infer the class specialization.
[environment]
python-version = "3.12"
class C[T]:
x: T
def __new__[S](cls, x: S) -> "C[tuple[S, S]]":
return object.__new__(cls)
reveal_type(C(1)) # revealed: C[tuple[int, int]]
reveal_type(C("hello")) # revealed: C[tuple[str, str]]
__new__ with arbitrary generic return typesWhen __new__ has method-level type variables in the return type that don't map to the class's type
parameters, the resolved return type should be used directly.
[environment]
python-version = "3.12"
class C:
def __new__[S](cls, x: S) -> S:
return x
reveal_type(C("foo")) # revealed: Literal["foo"]
reveal_type(C(1)) # revealed: Literal[1]
__new__ returning non-instance generic containers[environment]
python-version = "3.12"
class C:
def __new__[S](cls, x: S) -> list[S]:
return [x]
reveal_type(C("foo")) # revealed: list[str]
reveal_type(C(1)) # revealed: list[int]
__new__ call with unambiguous non-instance return typeclass C:
def __new__(cls, x: int) -> str:
return str(x)
# error: [invalid-argument-type]
reveal_type(C("foo")) # revealed: str
__new__ with generic return typesOverloaded __new__ methods should correctly resolve to the matching overload and infer the class
specialization from the overload's return type.
from typing import Generic, Iterable, TypeVar, overload
T = TypeVar("T")
T1 = TypeVar("T1")
T2 = TypeVar("T2")
class MyZip(Generic[T]):
@overload
def __new__(cls) -> "MyZip[object]": ...
@overload
def __new__(cls, iter1: Iterable[T1], iter2: Iterable[T2]) -> "MyZip[tuple[T1, T2]]": ...
def __new__(cls, *args, **kwargs) -> "MyZip[object]":
raise NotImplementedError
def check(a: tuple[int, ...], b: tuple[str, ...]) -> None:
reveal_type(MyZip(a, b)) # revealed: MyZip[tuple[int, str]]
reveal_type(MyZip()) # revealed: MyZip[object]
__new__ overloadsIf some __new__ overloads are instance-returning and some are not, the return type (and __init__
validation) are resolved correctly for each call site:
from __future__ import annotations
from typing import Any, Literal, overload
class A: ...
class B: ...
class C: ...
class D: ...
class Test:
@overload
def __new__(cls, x: A) -> A: ...
@overload
def __new__(cls, x: str) -> Test: ...
def __new__(cls, x: A | str) -> A | Test:
raise NotImplementedError()
def __init__(self, x: Literal["ok"]) -> None:
pass
# `A` matches the first `__new__` overload, which returns `A`, bypassing `__init__` since `A` is
# not a subtype of `Test`.
reveal_type(Test(A())) # revealed: A
# `str` returns `Test` from `__new__`, but `__init__` rejects `Literal["bad"]`.
# error: [invalid-argument-type]
reveal_type(Test("bad")) # revealed: Test
# `Literal["ok"]` returns `Test` from `__new__`, and is accepted by `__init__`.
reveal_type(Test("ok")) # revealed: Test
The same mechanism should also hold for a Self-returning overload:
from typing import overload
from typing_extensions import Self
class SimpleMixed:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> Self: ...
def __new__(cls, x: int | str) -> object: ...
def __init__(self, x: str) -> None: ...
reveal_type(SimpleMixed(1)) # revealed: int
reveal_type(SimpleMixed("foo")) # revealed: SimpleMixed
__new__ overloadsIf overload resolution for __new__ falls back to Unknown because the argument is Any or
Unknown, we should still validate downstream constructors:
from typing import Any, overload
from typing_extensions import Self
from missing import Unknown # type: ignore
class AmbiguousMixed:
@overload
def __new__(cls, x: int) -> Self: ...
@overload
def __new__(cls, x: str) -> str: ...
def __new__(cls, x: int | str) -> Self | str:
raise NotImplementedError
def __init__(self) -> None: ...
def _(a: Any, u: Unknown):
# error: [too-many-positional-arguments]
reveal_type(AmbiguousMixed(a)) # revealed: Unknown
# error: [too-many-positional-arguments]
reveal_type(AmbiguousMixed(u)) # revealed: Unknown
__new__ overloads should not become declaration-order dependentReversing the declaration order of the same mixed overload set should not change the result when
overload resolution falls back to Unknown.
from typing import Any, overload
from typing_extensions import Self
from missing import Unknown # type: ignore
class ReverseAmbiguousMixed:
@overload
def __new__(cls, x: str) -> str: ...
@overload
def __new__(cls, x: int) -> Self: ...
def __new__(cls, x: int | str) -> object:
raise NotImplementedError
def __init__(self) -> None: ...
def _(a: Any, u: Unknown):
# error: [too-many-positional-arguments]
reveal_type(ReverseAmbiguousMixed(a)) # revealed: Unknown
# error: [too-many-positional-arguments]
reveal_type(ReverseAmbiguousMixed(u)) # revealed: Unknown
__new__ should preserve matched return typeWhen all __new__ overloads return non-instance types, constructor return typing should still use
the matched overload's return type at each call site.
from typing import overload
class F:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> str: ...
def __new__(cls, x: int | str) -> object: ...
reveal_type(F(1)) # revealed: int
reveal_type(F("foo")) # revealed: str
__new__ should not invent an instance returnIf no overload matches, we should report Unknown rather than falling back to the class instance
type.
from typing import overload
class OnlyNonInstance:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> str: ...
def __new__(cls, x: int | str) -> object:
raise NotImplementedError
# error: [no-matching-overload]
reveal_type(OnlyNonInstance(1.2)) # revealed: Unknown
__new__ overloads should still validate __init__For generic classes, if an instance-returning __new__ overload matches, we still need to validate
__init__ even when another overload returns a non-instance type.
from typing import Generic, TypeVar, overload
from typing_extensions import Self
T = TypeVar("T")
class E(Generic[T]):
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: T) -> Self: ...
def __new__(cls, x: object) -> object: ...
def __init__(self, x: T, y: str) -> None: ...
# The `T -> Self` overload is instance-returning, so `__init__` must also be checked.
# error: [missing-argument]
reveal_type(E("foo")) # revealed: E[str]
__new__ should also normalize cls: type[T] -> T returnsThe same selected-overload path should treat self-like TypeVar returns as instance-returning.
from __future__ import annotations
from typing import Generic, TypeVar, overload
S = TypeVar("S")
T = TypeVar("T", bound="E")
class E(Generic[S]):
@overload
def __new__(cls, x: int, y: int) -> int: ...
@overload
def __new__(cls: type[T], x: S) -> T: ...
def __new__(cls, *args: object) -> object: ...
def __init__(self, x: S, y: str) -> None: ...
# The `type[T] -> T` overload is instance-returning, so `__init__` must also be checked.
# error: [missing-argument]
reveal_type(E("foo")) # revealed: E[str]
reveal_type(E(1, 2)) # revealed: int
__new__ should preserve constructor literal promotionWhen mixed __new__ overloads defer __init__ validation, the inferred constructor specialization
should still apply literal promotion from __init__.
from typing import Generic, TypeVar, overload
from typing_extensions import Self
T = TypeVar("T")
class E(Generic[T]):
@overload
def __new__(cls, tag: int, y: object) -> int: ...
@overload
def __new__(cls, tag: str, y: object) -> Self: ...
def __new__(cls, tag: int | str, y: object) -> object: ...
def __init__(self, tag: str, y: T) -> None: ...
reveal_type(E("ok", 1)) # revealed: E[int]
reveal_type(E(1, 1)) # revealed: int
__init__ checksfrom typing import overload
from typing_extensions import Self
class C:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> Self: ...
def __new__(cls, x: int | str) -> object: ...
def __init__(self, x: str, y: str) -> None: ...
class D:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> Self: ...
def __new__(cls, x: int | str) -> object: ...
def __init__(self, x: str) -> None: ...
def f(flag: bool) -> None:
ctor = C if flag else D
# `str -> Self` is selected on both constructor branches. `C.__init__` still
# requires `y`, so this should fail even after unioning constructor bindings.
# error: [missing-argument]
ctor("foo")
__init__ checksfrom typing import overload
from ty_extensions import Intersection
from typing_extensions import Self
class C:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> Self: ...
def __new__(cls, x: int | str) -> object: ...
def __init__(self, x: str, y: str) -> None: ...
class D:
def __init__(self, x: str) -> None: ...
def f(ctor: Intersection[type[C], type[D]]) -> None:
# `C.__new__` selects `str -> Self`, but `C.__init__` still rejects the call
# because `y` is missing. `D` accepts the call, so the intersection should
# succeed using only the `D` branch.
reveal_type(ctor("foo")) # revealed: D
__new__ should preserve specializationfrom typing import Generic, TypeVar
T = TypeVar("T")
class E(Generic[T]):
def __new__(cls, x: object):
return object.__new__(cls)
def __init__(self, x: T) -> None: ...
class F(Generic[T]):
def __new__(cls, x: object):
return object.__new__(cls)
def __init__(self, x: T) -> None: ...
def f(flag: bool) -> None:
ctor: type[E[int]] | type[F[int]]
if flag:
ctor = E
else:
ctor = F
reveal_type(ctor(1)) # revealed: E[int] | F[int]
__new__ should preserve specializationfrom typing import Generic, TypeVar
from ty_extensions import Intersection
T = TypeVar("T")
class E(Generic[T]):
def __new__(cls, x: object):
return object.__new__(cls)
def __init__(self, x: T) -> None: ...
class F(Generic[T]):
def __new__(cls, x: object):
return object.__new__(cls)
def __init__(self, x: T) -> None: ...
def f(ctor: Intersection[type[E[int]], type[F[int]]]) -> None:
reveal_type(ctor(1)) # revealed: E[int] & F[int]
__new__ returning a strict subclass preserves that return typeclass C:
def __new__(cls) -> "D":
return object.__new__(D)
class D(C): ...
# Preserve explicit strict-subclass constructor returns.
reveal_type(C()) # revealed: D
__new__ returning a strict subclass preserves that return type[environment]
python-version = "3.12"
class C[T]:
def __new__(cls, x: T) -> "D":
raise NotImplementedError
def __init__(self, x: object) -> None: ...
x: T
class D(C[int]): ...
reveal_type(C("foo")) # revealed: D
__new__ subtype return should inherit specialization from __init__[environment]
python-version = "3.12"
class C[T]:
def __new__(cls, x: object) -> "D[T]":
raise NotImplementedError
def __init__(self, x: T) -> None: ...
x: T
class D[T](C[T]): ...
reveal_type(C("foo")) # revealed: D[str]
__new__ preserving strict-subclass returnfrom typing import overload
class Base:
@overload
def __new__(cls, x: int) -> int: ...
@overload
def __new__(cls, x: str) -> "Child": ...
def __new__(cls, x: int | str) -> object: ...
def __init__(self, x: str) -> None: ...
class Child(Base): ...
reveal_type(Base(1)) # revealed: int
reveal_type(Base("foo")) # revealed: Child
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, x: T) -> None: ...
reveal_type(Box(1)) # revealed: Box[int]
__init__ self typesfrom __future__ import annotations
from typing import Generic, TypeVar, overload
T = TypeVar("T")
CT = TypeVar("CT")
class ClassSelector(Generic[T]):
@overload
def __init__(
self: ClassSelector[CT],
*,
default: CT,
class_: type[CT],
) -> None: ...
@overload
def __init__(
self: ClassSelector[CT | None],
*,
default: None = None,
class_: type[CT],
) -> None: ...
def __init__(self, *, default=None, class_=None): ...
class MyClass:
pass
a = ClassSelector(default=MyClass(), class_=MyClass)
reveal_type(a) # revealed: ClassSelector[MyClass]
b = ClassSelector(class_=MyClass)
reveal_type(b) # revealed: ClassSelector[MyClass | None]
# Explicit constructor specializations still reject incompatible inferred `self` types.
ClassSelector[int](class_=MyClass) # error: [invalid-argument-type]
class RequiredClassSelector(Generic[T]):
def __init__(self: RequiredClassSelector[CT | None], *, class_: type[CT]) -> None: ...
reveal_type(RequiredClassSelector(class_=MyClass)) # revealed: RequiredClassSelector[MyClass | None]
__init__ can remap constructor generic arguments via self annotationfrom typing import Generic, TypeVar
T1 = TypeVar("T1")
T2 = TypeVar("T2")
V1 = TypeVar("V1")
V2 = TypeVar("V2")
class Class6(Generic[T1, T2]):
def __init__(self: "Class6[V1, V2]", value1: V1, value2: V2) -> None: ...
reveal_type(Class6(0, "")) # revealed: Class6[int, str]
reveal_type(Class6[int, str](0, "")) # revealed: Class6[int, str]
class Class7(Generic[T1, T2]):
def __init__(self: "Class7[V2, V1]", value1: V1, value2: V2) -> None: ...
reveal_type(Class7(0, "")) # revealed: Class7[str, int]
reveal_type(Class7[str, int](0, "")) # revealed: Class7[str, int]
type[T] with a bound TypeVarfrom typing import TypeVar
class C:
def __new__(cls, x: int, y: str): ...
T = TypeVar("T", bound=C)
def f(cls: type[T]):
# error: [missing-argument] "No argument provided for required parameter `y` of constructor `C.__new__`"
cls(1)
# error: [invalid-argument-type] "Argument to constructor `C.__new__` is incorrect: Expected `str`, found `Literal[2]`"
cls(1, 2)
reveal_type(cls(1, "foo")) # revealed: T@f
class A:
def __init__(self, x: int) -> None:
self.x = x
class B:
def __init__(self, x: int) -> None:
self.x = x
def f(flag: bool):
cls = A if flag else B
reveal_type(cls(1)) # revealed: A | B
__init__ present on a superclassIf the __init__ method is defined on a superclass, we can still infer the signature of the
constructor from it.
class Base:
def __init__(self, x: int): ...
class Foo(Base): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of `Base.__init__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to `Base.__init__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
__init__def _(flag: bool) -> None:
class Foo:
if flag:
def __init__(self, x: int): ...
else:
def __init__(self, x: int, y: int = 1): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [invalid-argument-type] "Argument to `Foo.__init__` is incorrect: Expected `int`, found `Literal["1"]`"
# error: [invalid-argument-type] "Argument to `Foo.__init__` is incorrect: Expected `int`, found `Literal["1"]`"
reveal_type(Foo("1")) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of `Foo.__init__`"
# error: [missing-argument] "No argument provided for required parameter `x` of `Foo.__init__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to `Foo.__init__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
__init__class SomeCallable:
# TODO: at runtime `__init__` is checked to return `None` and
# a `TypeError` is raised if it doesn't. However, apparently
# this is not true when the descriptor is used as `__init__`.
# However, we may still want to check this.
def __call__(self, x: int) -> str:
return "a"
class Descriptor:
def __get__(self, instance, owner) -> SomeCallable:
return SomeCallable()
class Foo:
__init__: Descriptor = Descriptor()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `SomeCallable.__call__`"
reveal_type(Foo()) # revealed: Foo
__init__class Callable:
def __call__(self, x: int) -> None:
pass
class Foo:
__init__ = Callable()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `Callable.__call__`"
reveal_type(Foo()) # revealed: Foo
def _(flag: bool) -> None:
class Callable:
if flag:
def __call__(self, x: int) -> None:
pass
class Foo:
__init__ = Callable()
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
reveal_type(Foo(1)) # revealed: Foo
# TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
# but we currently infer the signature of `__call__` as unknown, so it accepts any arguments
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
reveal_type(Foo()) # revealed: Foo
__new__ and __init__ both presentBut they can also be compatible, but not identical. We should correctly report errors only for the mthod that would fail.
class Foo:
def __new__(cls, *args, **kwargs):
return object.__new__(cls)
def __init__(self, x: int) -> None:
self.x = x
# error: [missing-argument] "No argument provided for required parameter `x` of `Foo.__init__`"
reveal_type(Foo()) # revealed: Foo
reveal_type(Foo(1)) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to `Foo.__init__`: expected 2, got 3"
reveal_type(Foo(1, 2)) # revealed: Foo
class Foo:
def __new__(cls, x: int):
return object.__new__(cls)
def __init__(self, x: str) -> None:
self.x = x
# error: [invalid-argument-type] "Argument to `Foo.__init__` is incorrect: Expected `str`, found `Literal[1]`"
Foo(1)
# error: [invalid-argument-type] "Argument to constructor `Foo.__new__` is incorrect: Expected `int`, found `Literal["x"]`"
Foo("x")
import abc
class Foo:
def __new__(cls) -> "Foo":
return object.__new__(cls)
def __init__(self, x):
self.x = 42
# error: [missing-argument] "No argument provided for required parameter `x` of `Foo.__init__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to constructor `Foo.__new__`: expected 1, got 2"
reveal_type(Foo(42)) # revealed: Foo
class Foo2:
def __new__(cls, x) -> "Foo2":
return object.__new__(cls)
def __init__(self):
pass
# error: [missing-argument] "No argument provided for required parameter `x` of constructor `Foo2.__new__`"
reveal_type(Foo2()) # revealed: Foo2
# error: [too-many-positional-arguments] "Too many positional arguments to `Foo2.__init__`: expected 1, got 2"
reveal_type(Foo2(42)) # revealed: Foo2
class Foo3(metaclass=abc.ABCMeta):
def __new__(cls) -> "Foo3":
return object.__new__(cls)
def __init__(self, x):
self.x = 42
# error: [missing-argument] "No argument provided for required parameter `x` of `Foo3.__init__`"
reveal_type(Foo3()) # revealed: Foo3
# error: [too-many-positional-arguments] "Too many positional arguments to constructor `Foo3.__new__`: expected 1, got 2"
reveal_type(Foo3(42)) # revealed: Foo3
class Foo4(metaclass=abc.ABCMeta):
def __new__(cls, x) -> "Foo4":
return object.__new__(cls)
def __init__(self):
pass
# error: [missing-argument] "No argument provided for required parameter `x` of constructor `Foo4.__new__`"
reveal_type(Foo4()) # revealed: Foo4
# error: [too-many-positional-arguments] "Too many positional arguments to `Foo4.__init__`: expected 1, got 2"
reveal_type(Foo4(42)) # revealed: Foo4
__new__The __new__ method is always invoked on the class itself, never on the metaclass. This is
different from how other dunder methods like __lt__ are implicitly called (always on the
meta-type, never on the type itself).
from typing_extensions import Literal
class Meta(type):
def __new__(mcls, name, bases, namespace, /, **kwargs):
return super().__new__(mcls, name, bases, namespace)
def __lt__(cls, other) -> Literal[True]:
return True
class C(metaclass=Meta): ...
# No error is raised here, since we don't implicitly call `Meta.__new__`
reveal_type(C()) # revealed: C
# Meta.__lt__ is implicitly called here:
reveal_type(C < C) # revealed: Literal[True]