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 ofthree 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 isresponsible 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 constructorcall.
Metaclass__call__ Method¶
When evaluating a constructor call, a type checker should first check if theclass has a custom metaclass (a subclass oftype) that defines a__call__method. If so, it should evaluate the call of this method using the suppliedarguments. If the metaclass istype, this step can be skipped.
If the evaluated return type of the__call__ method indicates somethingother than an instance of the class being constructed, a type checker shouldassume that the metaclass__call__ method is overridingtype.__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 returnNoReturn to indicate that constructorcalls are not supported for that class.
classMeta(type):def__call__(cls,*args,**kwargs)->NoReturn:raiseTypeError("Cannot instantiate class")classMyClass(metaclass=Meta):def__new__(cls,*args,**kwargs)->Self:returnsuper().__new__(cls,*args,**kwargs)assert_type(MyClass(),Never)
If no return type annotation is provided for__call__, a type checker mayassume that it does not overridetype.__call__ in a special manner andproceed as though the return type is an instance of the type specified bythecls parameter.
__new__ Method¶
After the metaclass__call__ method has been evaluated, a type checkershould evaluate the__new__ method of the class (if applicable) usingthe supplied arguments. This step should be skipped if the class does notdefine a__new__ method and does not inherit a__new__ method froma base class other thanobject.
If the class is generic and explicitly specialized, the type checker shouldpartially specialize the__new__ method using the supplied type arguments.If the class is not explicitly specialized, class-scoped type variables shouldbe solved using the supplied arguments passed to the constructor call.
classMyClass[T]:def__new__(cls,x:T)->Self:returnsuper().__new__(cls)# Constructor calls for specialized classesassert_type(MyClass[int](1),MyClass[int])assert_type(MyClass[float](1),MyClass[float])MyClass[int](1.0)# Type error# Constructor calls for non-specialized classesassert_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 leftunsolved, allowing the__init__ method (if applicable) to be used to solvethem.
classMyClass[T]:def__new__(cls,*args,**kwargs)->Self:returnsuper().__new__(cls)def__init__(self,x:T)->None:passassert_type(MyClass(1),MyClass[int])assert_type(MyClass(""),MyClass[str])
For most classes, the return type for the__new__ method is typicallySelf, but other types are also allowed. For example, the__new__method may return an instance of a subclass or an instance of some completelyunrelated 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 thetype.__call__ method. If the__new__ method return type is a union withone or more members that are not the class being constructed (or a subclassthereof), a type checker should likewise assume that the__init__ methodwill not be called.
classMyClass:def__new__(cls)->int:return0# In this case, the __init__ method should not be considered# by the type checker when evaluating a constructor call.def__init__(self,x:int):passassert_type(MyClass(),int)
For purposes of this test, an explicit return type ofAny (or aunion containingAny) should be treated as a type that is not an instanceof the class being constructed.
classMyClass:def__new__(cls)->Any:return0# The __init__ method will not be called in this case, so# it should not be evaluated.def__init__(self,x:int):passassert_type(MyClass(),Any)
If the return type of__new__ is not annotated, a type checker may assumethat the return type isSelf 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 overridethe specialized class type and return a class instance that is specializedwith different type arguments.
classMyClass[T]:def__new__(cls,*args,**kwargs)->"MyClass[list[T]]":...assert_type(MyClass[int](),MyClass[list[int]])
If thecls parameter within the__new__ method is not annotated, typecheckers should infer a type oftype[Self]. Regardless of whether thetype of thecls parameter is explicit or inferred, the type checker shouldbind the class being constructed to thecls parameter and report any typeerrors that arise during binding.
classMyClass[T]:def__new__(cls:"type[MyClass[int]]")->"MyClass[int]":...MyClass()# OKMyClass[int]()# OKMyClass[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 classis generic and explicitly specialized (or specialized via the__new__ methodreturn type), the type checker should partially specialize the__init__method using the supplied type arguments. If the class is not explicitlyspecialized, class-scoped type variables should be solved using the suppliedarguments passed to the constructor call.
This step should be skipped if the class does not define an__init__ methodand does not inherit an__init__ method from a base class other thanobject.
classMyClass[T]:def__init__(self,x:T)->None:...# Constructor calls for specialized classesassert_type(MyClass[int](1),MyClass[int])assert_type(MyClass[float](1),MyClass[float])MyClass[int](1.0)# Type error# Constructor calls for non-specialized classesassert_type(MyClass(1),MyClass[int])assert_type(MyClass(1.0),MyClass[float])
If theself parameter within the__init__ method is not annotated, typecheckers should infer a type ofSelf. Regardless of whether theselfparameter type is explicit or inferred, a type checker should bind the classbeing constructed to this parameter and report any type errors that ariseduring binding.
classMyClass[T]:def__init__(self:"MyClass[int]")->None:...MyClass()# OKMyClass[int]()# OKMyClass[str]()# Type Error
The return type for__init__ is alwaysNone, which means themethod cannot influence the return type of the constructor call by specifyinga return type. There are cases where it is desirable for the__init__ methodto influence the return type, especially when the__init__ method isoverloaded. To enable this, type checkers should allow theself parameterto be annotated with a type that influences the resulting type of theconstructor call.
classMyClass1[T]:@overloaddef__init__(self:"MyClass1[list[int]]",value:int)->None:...@overloaddef__init__(self:"MyClass1[set[str]]",value:str)->None:...@overloaddef__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 theselfannotation of an__init__ method to influence the return type of theconstructor call.
classMyClass2[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])classMyClass3[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 theself annotationbecause such use can lead to ambiguous or nonsensical type evaluation results.Type checkers should report an error if a class-scoped type variable is usedwithin a type annotation for theself parameter in an__init__ method.
classMyClass4[T1,T2]:# The ``self`` annotation should result in a type errordef__init__(self:"MyClass4[T2, T1]")->None:...
Classes Without__new__ and__init__ Methods¶
If a class does not define a__new__ method or__init__ method anddoes not inherit either of these methods from a base class other thanobject, a type checker should evaluate the argument list using the__new__ and__init__ methods from theobject class.
classMyClass5:passMyClass5()# OKMyClass5(1)# Type error
Constructor Calls for type[T]¶
When a value of typetype[T] (whereT is a concrete class or a typevariable) is called, a type checker should evaluate the constructor call as ifit is being made on the classT (or the class that represents the upper boundof type variableT). This means the type checker should use the__call__method ofT’s metaclass and the__new__ and__init__ methods ofTto evaluate the constructor call.
It should be noted that such code could be unsafe because the typetype[T]may represent subclasses ofT, and those subclasses could redefine the__new__ and__init__ methods in a way that is incompatible with thebase class. Likewise, the metaclass ofT 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, itstype variables should be solved using the arguments passed to the__new__and__init__ methods. If one or more type variables are not solved duringthese method evaluations, they should take on their default values.
fromtypingimportAny,Self,assert_typeclassMyClass1[T1,T2]:def__new__(cls,x:T1)->Self:...assert_type(MyClass1(1),MyClass1[int,Any])classMyClass2[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 haveconsistent signatures.
classMyClass:def__new__(cls)->Self:returnsuper().__new__(cls)# Type error: __new__ and __init__ have inconsistent signaturesdef__init__(self,x:str)->None:pass
Converting a Constructor to Callable¶
Class objects are callable, which means the type of a class object can beassignable to a callable type.
defaccepts_callable[**P,R](cb:Callable[P,R])->Callable[P,R]:returncbclassMyClass:def__init__(self,x:int)->None:passreveal_type(accepts_callable(MyClass))# ``def (x: int) -> MyClass``
When converting a class to a callable type, a type checker should use thefollowing rules, which reflect the same rules specified above for evaluatingconstructor calls:
If the class has a custom metaclass that defines a
__call__methodthat is annotated with a return type other than a subclass of theclass being constructed (or a union that contains such a type), a typechecker should assume that the metaclass__call__method is overridingtype.__call__in some special manner. In this case, the callable shouldbe 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 uncommoncase. In the more typical case where there is no custom metaclass thatoverridestype.__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 anannotated 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__methodfrom a base class other thanobject, a type checker should synthesize acallable from the parameters and return type of that method after it is boundto the class.If the return type of the method in step 2 evaluates to a type that is not asubclass of the class being constructed (or a union that includes such aclass), the final callable type is based on the result of step 2, and theconversion process is complete. The
__init__method is ignored in thiscase. This is consistent with the runtime behavior of thetype.__call__method.If the class defines an
__init__method or inherits an__init__methodfrom a base class other thanobject, a callable type should be synthesizedfrom the parameters of the__init__method after it is bound to the classinstance resulting from step 2. The return type of this synthesized callableshould be the concrete value ofSelf.If step 2 and 4 both produce no result because the class does not define orinherit 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 finalresult of the conversion process is the union of these types. This willreflect the callable signatures of the applicable
__new__and__init__methods.
classA:""" No __new__ or __init__ """passclassB:""" __new__ and __init__ """def__new__(cls,*args,**kwargs)->Self:...def__init__(self,x:int)->None:...classC:""" __new__ but no __init__ """def__new__(cls,x:int)->int:...classCustomMeta(type):def__call__(cls)->NoReturn:raiseNotImplementedError("Class not constructable")classD(metaclass=CustomMeta):""" Custom metaclass that overrides type.__call__ """def__new__(cls,*args,**kwargs)->Self:""" This __new__ is ignored for purposes of conversion """passclassE:""" __new__ that causes __init__ to be ignored """def__new__(cls)->A:returnA.__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 callabletype should be synthesized from the overloads. The resulting callable typeitself will be overloaded.
classMyClass:@overloaddef__init__(self,x:int)->None:...@overloaddef__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-scopedtype parameters that appear within the signature, but these type parameters shouldbe 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 synthesizedcallable.
classMyClass[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]``