Constructors¶
Calls to constructors require special handling within type checkers.
Constructor Calls¶
At runtime, a call to a class’ constructor typically results in the invocation of three methods in the following order:
The
__call__method of the metaclass (which is typically supplied by thetypeclass but can be overridden by a custom metaclass and which is responsible for calling the next two methods)The
__new__static method of the classThe
__init__instance method of the class
Type checkers should mirror this runtime behavior when analyzing a constructor call.
Metaclass __call__ Method¶
When evaluating a constructor call, a type checker should first check if the
class has a custom metaclass (a subclass of type) that defines a __call__
method. If so, it should evaluate the call of this method using the supplied
arguments. If the metaclass is type, this step can be skipped.
If the evaluated return type of the __call__ method indicates something
other than an instance of the class being constructed, a type checker should
assume that the metaclass __call__ method is overriding type.__call__
in some special manner, and it should not attempt to evaluate the __new__
or __init__ methods on the class. For example, some metaclass __call__
methods are annotated to return NoReturn to indicate that constructor
calls are not supported for that class.
class Meta(type): def __call__(cls, *args, **kwargs) -> NoReturn: raise TypeError("Cannot instantiate class") class MyClass(metaclass=Meta): def __new__(cls, *args, **kwargs) -> Self: return super().__new__(cls, *args, **kwargs) assert_type(MyClass(), Never)
If no return type annotation is provided for __call__, a type checker may
assume that it does not override type.__call__ in a special manner and
proceed as though the return type is an instance of the type specified by
the cls parameter.
__new__ Method¶
After the metaclass __call__ method has been evaluated, a type checker
should evaluate the __new__ method of the class (if applicable) using
the supplied arguments. This step should be skipped if the class does not
define a __new__ method and does not inherit a __new__ method from
a base class other than object.
If the class is generic and explicitly specialized, the type checker should
partially specialize the __new__ method using the supplied type arguments.
If the class is not explicitly specialized, class-scoped type variables should
be solved using the supplied arguments passed to the constructor call.
class MyClass[T]: def __new__(cls, x: T) -> Self: return super().__new__(cls) # Constructor calls for specialized classes assert_type(MyClass[int](1), MyClass[int]) assert_type(MyClass[float](1), MyClass[float]) MyClass[int](1.0) # Type error # Constructor calls for non-specialized classes assert_type(MyClass(1), MyClass[int]) assert_type(MyClass(1.0), MyClass[float])
If any class-scoped type variables are not solved when evaluating the __new__
method call using the supplied arguments, these type variables should be left
unsolved, allowing the __init__ method (if applicable) to be used to solve
them.
class MyClass[T]: def __new__(cls, *args, **kwargs) -> Self: return super().__new__(cls) def __init__(self, x: T) -> None: pass assert_type(MyClass(1), MyClass[int]) assert_type(MyClass(""), MyClass[str])
For most classes, the return type for the __new__ method is typically
Self, but other types are also allowed. For example, the __new__
method may return an instance of a subclass or an instance of some completely
unrelated class.
If the evaluated return type of __new__ is not the class being constructed
(or a subclass thereof), a type checker should assume that the __init__
method will not be called. This is consistent with the runtime behavior of the
type.__call__ method. If the __new__ method return type is a union with
one or more members that are not the class being constructed (or a subclass
thereof), a type checker should likewise assume that the __init__ method
will not be called.
class MyClass: def __new__(cls) -> int: return 0 # In this case, the __init__ method should not be considered # by the type checker when evaluating a constructor call. def __init__(self, x: int): pass assert_type(MyClass(), int)
For purposes of this test, an explicit return type of Any (or a
union containing Any) should be treated as a type that is not an instance
of the class being constructed.
class MyClass: def __new__(cls) -> Any: return 0 # The __init__ method will not be called in this case, so # it should not be evaluated. def __init__(self, x: int): pass assert_type(MyClass(), Any)
If the return type of __new__ is not annotated, a type checker may assume
that the return type is Self and proceed with the assumption that the
__init__ method will be called.
If the class is generic, it is possible for a __new__ method to override
the specialized class type and return a class instance that is specialized
with different type arguments.
class MyClass[T]: def __new__(cls, *args, **kwargs) -> "MyClass[list[T]]": ... assert_type(MyClass[int](), MyClass[list[int]])
If the cls parameter within the __new__ method is not annotated, type
checkers should infer a type of type[Self]. Regardless of whether the
type of the cls parameter is explicit or inferred, the type checker should
bind the class being constructed to the cls parameter and report any type
errors that arise during binding.
class MyClass[T]: def __new__(cls: "type[MyClass[int]]") -> "MyClass[int]": ... MyClass() # OK MyClass[int]() # OK MyClass[str]() # Type Error
__init__ Method¶
After evaluating the __new__ method, a type checker should evaluate the
__init__ method (if applicable) using the supplied arguments. If the class
is generic and explicitly specialized (or specialized via the __new__ method
return type), the type checker should partially specialize the __init__
method using the supplied type arguments. If the class is not explicitly
specialized, class-scoped type variables should be solved using the supplied
arguments passed to the constructor call.
This step should be skipped if the class does not define an __init__ method
and does not inherit an __init__ method from a base class other than
object.
class MyClass[T]: def __init__(self, x: T) -> None: ... # Constructor calls for specialized classes assert_type(MyClass[int](1), MyClass[int]) assert_type(MyClass[float](1), MyClass[float]) MyClass[int](1.0) # Type error # Constructor calls for non-specialized classes assert_type(MyClass(1), MyClass[int]) assert_type(MyClass(1.0), MyClass[float])
If the self parameter within the __init__ method is not annotated, type
checkers should infer a type of Self. Regardless of whether the self
parameter type is explicit or inferred, a type checker should bind the class
being constructed to this parameter and report any type errors that arise
during binding.
class MyClass[T]: def __init__(self: "MyClass[int]") -> None: ... MyClass() # OK MyClass[int]() # OK MyClass[str]() # Type Error
The return type for __init__ is always None, which means the
method cannot influence the return type of the constructor call by specifying
a return type. There are cases where it is desirable for the __init__ method
to influence the return type, especially when the __init__ method is
overloaded. To enable this, type checkers should allow the self parameter
to be annotated with a type that influences the resulting type of the
constructor call.
class MyClass1[T]: @overload def __init__(self: "MyClass1[list[int]]", value: int) -> None: ... @overload def __init__(self: "MyClass1[set[str]]", value: str) -> None: ... @overload def __init__(self, value: T) -> None: ... assert_type(MyClass1(0), MyClass1[list[int]]) assert_type(MyClass1[int](3), MyClass1[int]) assert_type(MyClass1(""), MyClass1[set[str]]) assert_type(MyClass1(3.0), MyClass1[float])
Function-scoped type variables can also be used in the self
annotation of an __init__ method to influence the return type of the
constructor call.
class MyClass2[T1, T2]: def __init__[V1, V2](self: "MyClass2[V1, V2]", value1: V1, value2: V2) -> None: ... assert_type(MyClass2(0, ""), MyClass2[int, str]) assert_type(MyClass2[int, str](0, ""), MyClass2[int, str]) class MyClass3[T1, T2]: def __init__[V1, V2](self: "MyClass3[V2, V1]", value1: V1, value2: V2) -> None: ... assert_type(MyClass3(0, ""), MyClass3[str, int]) assert_type(MyClass3[str, int](0, ""), MyClass3[str, int])
Class-scoped type variables should not be used in the self annotation
because such use can lead to ambiguous or nonsensical type evaluation results.
Type checkers should report an error if a class-scoped type variable is used
within a type annotation for the self parameter in an __init__ method.
class MyClass4[T1, T2]: # The ``self`` annotation should result in a type error def __init__(self: "MyClass4[T2, T1]") -> None: ...
Classes Without __new__ and __init__ Methods¶
If a class does not define a __new__ method or __init__ method and
does not inherit either of these methods from a base class other than
object, a type checker should evaluate the argument list using the
__new__ and __init__ methods from the object class.
class MyClass5: pass MyClass5() # OK MyClass5(1) # Type error
Constructor Calls for type[T]¶
When a value of type type[T] (where T is a concrete class or a type
variable) is called, a type checker should evaluate the constructor call as if
it is being made on the class T (or the class that represents the upper bound
of type variable T). This means the type checker should use the __call__
method of T’s metaclass and the __new__ and __init__ methods of T
to evaluate the constructor call.
It should be noted that such code could be unsafe because the type type[T]
may represent subclasses of T, and those subclasses could redefine the
__new__ and __init__ methods in a way that is incompatible with the
base class. Likewise, the metaclass of T could redefine the __call__
method in a way that is incompatible with the base metaclass.
Specialization During Construction¶
As discussed above, if a class is generic and not explicitly specialized, its
type variables should be solved using the arguments passed to the __new__
and __init__ methods. If one or more type variables are not solved during
these method evaluations, they should take on their default values.
from typing import Any, Self, assert_type class MyClass1[T1, T2]: def __new__(cls, x: T1) -> Self: ... assert_type(MyClass1(1), MyClass1[int, Any]) class MyClass2[T1, T3 = str]: def __new__(cls, x: T1) -> Self: ... assert_type(MyClass2(1), MyClass2[int, str])
Consistency of __new__ and __init__¶
Type checkers may optionally validate that the __new__ and __init__
methods for a class have consistent signatures.
class MyClass: def __new__(cls) -> Self: return super().__new__(cls) # Type error: __new__ and __init__ have inconsistent signatures def __init__(self, x: str) -> None: pass
Converting a Constructor to Callable¶
Class objects are callable, which means the type of a class object can be assignable to a callable type.
def accepts_callable[**P, R](cb: Callable[P, R]) -> Callable[P, R]: return cb class MyClass: def __init__(self, x: int) -> None: pass reveal_type(accepts_callable(MyClass)) # ``def (x: int) -> MyClass``
When converting a class to a callable type, a type checker should use the following rules, which reflect the same rules specified above for evaluating constructor calls:
If the class has a custom metaclass that defines a
__call__method that is annotated with a return type other than a subclass of the class being constructed (or a union that contains such a type), a type checker should assume that the metaclass__call__method is overridingtype.__call__in some special manner. In this case, the callable should be synthesized from the parameters and return type of the metaclass__call__method after it is bound to the class, and the__new__or__init__methods (if present) should be ignored. This is an uncommon case. In the more typical case where there is no custom metaclass that overridestype.__call__in a special manner, the metaclass__call__signature should be ignored for purposes of converting to a callable type. If a custom metaclass__call__method is present but does not have an annotated return type, type checkers may assume that the method acts liketype.__call__and proceed to the next step.If the class defines a
__new__method or inherits a__new__method from a base class other thanobject, a type checker should synthesize a callable from the parameters and return type of that method after it is bound to the class.If the return type of the method in step 2 evaluates to a type that is not a subclass of the class being constructed (or a union that includes such a class), the final callable type is based on the result of step 2, and the conversion process is complete. The
__init__method is ignored in this case. This is consistent with the runtime behavior of thetype.__call__method.If the class defines an
__init__method or inherits an__init__method from a base class other thanobject, a callable type should be synthesized from the parameters of the__init__method after it is bound to the class instance resulting from step 2. The return type of this synthesized callable should be the concrete value ofSelf.If step 2 and 4 both produce no result because the class does not define or inherit a
__new__or__init__method from a class other thanobject, the type checker should synthesize callable types from the__new__and__init__methods for theobjectclass.Steps 2, 4 and 5 will produce either one or two callable types. The final result of the conversion process is the union of these types. This will reflect the callable signatures of the applicable
__new__and__init__methods.
class A: """ No __new__ or __init__ """ pass class B: """ __new__ and __init__ """ def __new__(cls, *args, **kwargs) -> Self: ... def __init__(self, x: int) -> None: ... class C: """ __new__ but no __init__ """ def __new__(cls, x: int) -> int: ... class CustomMeta(type): def __call__(cls) -> NoReturn: raise NotImplementedError("Class not constructable") class D(metaclass=CustomMeta): """ Custom metaclass that overrides type.__call__ """ def __new__(cls, *args, **kwargs) -> Self: """ This __new__ is ignored for purposes of conversion """ pass class E: """ __new__ that causes __init__ to be ignored """ def __new__(cls) -> A: return A.__new__(cls) def __init__(self, x: int) -> None: """ This __init__ is ignored for purposes of conversion """ ... reveal_type(accepts_callable(A)) # ``def () -> A`` reveal_type(accepts_callable(B)) # ``def (*args, **kwargs) -> B | def (x: int) -> B`` reveal_type(accepts_callable(C)) # ``def (x: int) -> int`` reveal_type(accepts_callable(D)) # ``def () -> NoReturn`` reveal_type(accepts_callable(E)) # ``def () -> A``
If the __init__ or __new__ method is overloaded, the callable
type should be synthesized from the overloads. The resulting callable type
itself will be overloaded.
class MyClass: @overload def __init__(self, x: int) -> None: ... @overload def __init__(self, x: str) -> None: ... reveal_type(accepts_callable(MyClass)) # overload of ``def (x: int) -> MyClass`` and ``def (x: str) -> MyClass``
If the class is generic, the synthesized callable should include any class-scoped
type parameters that appear within the signature, but these type parameters should
be converted to function-scoped type parameters for the callable.
Any function-scoped type parameters in the __init__ or __new__
method should also be included as function-scoped type parameters in the synthesized
callable.
class MyClass[T]: def __init__[V](self, x: T, y: list[V], z: V) -> None: ... reveal_type(accepts_callable(MyClass)) # ``def [T, V] (x: T, y: list[V], z: V) -> MyClass[T]``
Metaclass Constructors¶
A class object is itself an instance of its metaclass, so the creation of a class is also a constructor call, one made on the metaclass. While the sections above describe how a metaclass participates in the construction of instances of a class, the following sections describe the construction of class objects themselves.
A metaclass constructor is invoked in one of two ways:
Directly, by calling the metaclass with a class name, a tuple of base classes, and a namespace dictionary (for example,
Meta(name, bases, namespace)), optionally along with additional keyword arguments.Implicitly, by a
classstatement, which assembles these three arguments from the statement and the class body and then calls the metaclass.
In both cases, the metaclass call should be evaluated using the same rules
described in the sections above: the __call__() method of the metaclass’s
own metaclass (typically type.__call__()) is invoked, which in turn calls
the __new__() and __init__() methods of the metaclass. These methods are
typically inherited from type, whose type definitions require special
handling by type checkers, as described below.
The following example illustrates these rules applied to metaclass calls:
class MetaMeta(type): def __call__(cls, *args, **kwargs) -> Never: raise TypeError("Classes cannot be created with this metaclass") class Meta1(type, metaclass=MetaMeta): pass # The __call__() method of the metaclass's own metaclass is evaluated first: assert_type(Meta1("A", (), {}), Never) class Meta2(type): def __new__( mcls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], *, key: int, ): return super().__new__(mcls, name, bases, namespace) # Then, the __new__ and __init__ methods of the metaclass are evaluated: Meta2("B", (), {}, key=1) # OK, evaluates to an instance of Meta2 Meta2("B", (), {}) # Type error: missing argument "key" class Meta3(type): def __new__( mcls, name: str, bases: tuple[type, ...], namespace: dict[str, Any] ) -> int: return 0 # Not evaluated, as __new__ does not return an instance of Meta3: def __init__(cls, x: str) -> None: pass assert_type(Meta3("C", (), {}), int)
The type Constructor¶
In addition to being the default metaclass, type serves a second purpose:
when called with a single argument, it returns the type of that argument rather than
creating a new class. type therefore supports two distinct call forms,
which are distinguished by the number of positional arguments:
type(obj, /)returns the class ofobj.type(name, bases, dict, /, **kwds)creates and returns a new class.
These two forms are typically declared as overloads of the __new__() and
__init__() methods in the type definition of type. Both forms require
special-case handling by type checkers.
Although the single-argument form is typically declared with a return type of
type, type checkers should special-case this form and evaluate its result
as type[T], where T is the type of the argument.
def func(x: int, y: int | str) -> None: assert_type(type(x), type[int]) assert_type(type(y), type[int] | type[str])
At runtime, the single-argument form applies only when the class being called
is type itself, and is not inherited by metaclasses: a single-argument call
to a subclass of type raises a TypeError.
class Meta(type): pass assert_type(type(1), type[int]) # OK, uses the single-argument form Meta(1) # Type error: single-argument form does not apply to subclasses Meta("A", (), {}) # OK, uses the three-argument form
This special-casing applies only to the __new__() and __init__()
methods inherited from type. If a metaclass defines its own __new__()
method that accepts a single argument, calls to it should be evaluated
using the rules for regular constructor calls described earlier in this
chapter.
The evaluated return type of the three-argument form is an instance of the
metaclass being called, consistent with the return type definition of
type.__new__(). Type checkers may infer a more precise type for the returned
class object, for example, one equivalent to a class defined by a class
statement with the given name, base classes, and namespace.
Class Statements¶
When a class statement is executed, the runtime performs the following
steps to create the new class object (see Metaclasses):
The metaclass is determined. If a
metaclasskeyword argument is present in the class statement’s argument list, it is used as a candidate; otherwise,typeis. The most derived metaclass among the candidate and the metaclasses of all base classes is selected. If no candidate is a (non-strict) subclass of all of the others, aTypeErroris raised (see Determining the appropriate metaclass).The class namespace is prepared. If the metaclass has a
__prepare__attribute, it is called asMeta.__prepare__(name, bases, **kwds), and its result is used as the namespace object.The class body is executed within this namespace.
The metaclass is called as
Meta(name, bases, namespace, **kwds), wherekwdsconsists of the keyword arguments that appear in the class statement’s argument list, excludingmetaclassitself.
Type checkers may report an error for a class statement whose base classes have incompatible metaclasses.
Type checkers should validate keyword arguments in a class statement’s
argument list (other than metaclass) by evaluating the implied metaclass
call using the constructor call rules described in Constructor Calls.
class Meta(type): def __new__( mcls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], *, key: int, ): return super().__new__(mcls, name, bases, namespace) class MyClass1(metaclass=Meta, key=3): # OK pass class MyClass2(metaclass=Meta, key=""): # Type error: wrong type for "key" pass class MyClass3(metaclass=Meta): # Type error: missing argument "key" pass Meta("MyClass4", (), {}, key=3) # OK Meta("MyClass5", (), {}, key="") # Type error: wrong type for "key"
Keyword arguments in a direct metaclass call (such as the last two calls in the example above) require no special handling: they are validated as part of evaluating the call using the standard constructor call rules.
Type checkers should honor the evaluated retunr type of the implied metaclass call, even if the evaluated type isn’t a class:
class Meta(type):
def __new__(cls, *args: object, **kwargs: obect) -> int:
return 1
class MyClass6(metaclass=Meta):
pass
assert_type(MyClass6, int)
Type checkers may validate the implied call to __prepare__:
class Meta(type): @classmethod def __prepare__(mcls, name: str, bases: tuple[type, ...]): # No **kwds return {} def __new__( mcls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], *, key: int, ): return super().__new__(mcls, name, bases, namespace) # The 'key' argument may result in a type checker error: class MyClass6(metaclass=Meta, key=3): pass
The metaclass argument can also be an arbitrary callable that is not a subclass
of type. Support for this pattern is currently unspecified.
The __init_subclass__() Method¶
type.__new__() invokes the __init_subclass__()
method of the parent class (the class that follows the newly created class in
its method resolution order) passing the newly created class as
cls along with the keyword arguments supplied to the metaclass constructor
(see Customizing class creation).
__init_subclass__() is implicitly a class method: it is converted to
a classmethod even when it is not explicitly decorated as one, and it is
not called for the class that defines it, only for its subclasses. Type checkers
should treat it as a classmethod if it isn’t explicitly defined as one.
If the metaclass of the class being defined does not define its own
__new__() method (including when no explicit metaclass
is specified), type checkers should validate the keyword arguments in a
class statement’s argument list against the __init_subclass__() method of
the parent class.
class Base: def __init_subclass__(cls, *, flag: bool = False) -> None: super().__init_subclass__() class MyClass1(Base, flag=True): # OK pass class MyClass2(Base, flag=""): # Type error: wrong type for "flag" pass class MyClass3(Base, other=1): # Type error: Base.__init_subclass__() got an unexpected keyword argument 'other' pass class MyClass4(other=1): # Type error: MyClass4.__init_subclass__() takes no keyword arguments pass
A metaclass __init__() method has no effect on this rule: when the
metaclass does not define its own __new__() method, type.__new__()
still forwards the keyword arguments to __init_subclass__(), so
the keyword arguments should satisfy both the metaclass __init__()
method (as part of validating the implied metaclass call) and the
__init_subclass__() method of the parent class.
class MetaInit(type): def __init__( cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any], *, key: int, ) -> None: super().__init__(name, bases, namespace) # Type error: "key" is accepted by MetaInit.__init__(), but type.__new__() # forwards it to Base.__init_subclass__(), which does not accept it: class MyClass5(Base, metaclass=MetaInit, key=1): pass
The same forwarding occurs when the metaclass is called directly:
type("D", (Base,), {}, flag=True) passes flag to
Base.__init_subclass__(). Type checkers may validate keyword
arguments in such calls against the __init_subclass__() method of
the parent class when the base classes can be statically determined.
If the metaclass defines its own __new__() method that accepts keyword
arguments only through a **kwargs parameter, whether these arguments are
forwarded to type.__new__() (and from there to
__init_subclass__()) cannot generally be determined statically.
In this situation, type checkers may additionally validate the keyword
arguments against the __init_subclass__() method of the parent
class.