Movatterモバイル変換


[0]ホーム

URL:


ContentsMenuExpandLight modeDark modeAuto light/dark, in light modeAuto light/dark, in dark modeSkip to content
mypy 1.19.1 documentation
Logo
mypy 1.19.1 documentation

First steps

Type system reference

Configuring and running mypy

Miscellaneous

Project Links

Back to top

Generics

This section explains how you can define your own generic classes that takeone or more type arguments, similar to built-in types such aslist[T].User-defined generics are a moderately advanced feature and you can get farwithout ever using them – feel free to skip this section and come back later.

Defining generic classes

The built-in collection classes are generic classes. Generic typesaccept one or more type arguments within[...], which can bearbitrary types. For example, the typedict[int,str] has thetype argumentsint andstr, andlist[int] has the typeargumentint.

Programs can also define new generic classes. Here is a very simplegeneric class that represents a stack (using the syntax introduced inPython 3.12):

classStack[T]:def__init__(self)->None:# Create an empty list with items of type Tself.items:list[T]=[]defpush(self,item:T)->None:self.items.append(item)defpop(self)->T:returnself.items.pop()defempty(self)->bool:returnnotself.items

There are two syntax variants for defining generic classes in Python.Python 3.12 introduced anew dedicated syntaxfor defining generic classes (and also functions and type aliases, whichwe will discuss later). The above example used the new syntax. Most examples aregiven using both the new and the old (or legacy) syntax variants.Unless mentioned otherwise, they work the same – but the new syntaxis more readable and more convenient.

Here is the same example using the old syntax (required for Python 3.11and earlier, but also supported on newer Python versions):

fromtypingimportTypeVar,GenericT=TypeVar('T')# Define type variable "T"classStack(Generic[T]):def__init__(self)->None:# Create an empty list with items of type Tself.items:list[T]=[]defpush(self,item:T)->None:self.items.append(item)defpop(self)->T:returnself.items.pop()defempty(self)->bool:returnnotself.items

Note

There are currently no plans to deprecate the legacy syntax.You can freely mix code using the new and old syntax variants,even within a single file (butnot within a single class).

TheStack class can be used to represent a stack of any type:Stack[int],Stack[tuple[int,str]], etc. You can think ofStack[int] as referring to the definition ofStack above,but with all instances ofT replaced withint.

UsingStack is similar to built-in container types:

# Construct an empty Stack[int] instancestack=Stack[int]()stack.push(2)stack.pop()# error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"stack.push('x')stack2:Stack[str]=Stack()stack2.push('x')

Construction of instances of generic types is type checked (Python 3.12 syntax):

classBox[T]:def__init__(self,content:T)->None:self.content=contentBox(1)# OK, inferred type is Box[int]Box[int](1)# Also OK# error: Argument 1 to "Box" has incompatible type "str"; expected "int"Box[int]('some string')

Here is the definition ofBox using the legacy syntax (Python 3.11 and earlier):

fromtypingimportTypeVar,GenericT=TypeVar('T')classBox(Generic[T]):def__init__(self,content:T)->None:self.content=content

Note

Before moving on, let’s clarify some terminology.The nameT inclassStack[T] orclassStack(Generic[T])declares atype parameterT (of classStack).T is also called atype variable, especially in a type annotation,such as in the signature ofpush above.When the typeStack[...] is used in a type annotation, the typewithin square brackets is called atype argument.This is similar to the distinction between function parameters and arguments.

Defining subclasses of generic classes

User-defined generic classes and generic classes defined intypingcan be used as a base class for another class (generic or non-generic). Forexample (Python 3.12 syntax):

fromtypingimportMapping,Iterator# This is a generic subclass of MappingclassMyMap[KT,VT](Mapping[KT,VT]):def__getitem__(self,k:KT)->VT:...def__iter__(self)->Iterator[KT]:...def__len__(self)->int:...items:MyMap[str,int]# OK# This is a non-generic subclass of dictclassStrDict(dict[str,str]):def__str__(self)->str:returnf'StrDict({super().__str__()})'data:StrDict[int,int]# Error! StrDict is not genericdata2:StrDict# OK# This is a user-defined generic classclassReceiver[T]:defaccept(self,value:T)->None:...# This is a generic subclass of ReceiverclassAdvancedReceiver[T](Receiver[T]):...

Here is the above example using the legacy syntax (Python 3.11 and earlier):

fromtypingimportGeneric,TypeVar,Mapping,IteratorKT=TypeVar('KT')VT=TypeVar('VT')# This is a generic subclass of MappingclassMyMap(Mapping[KT,VT]):def__getitem__(self,k:KT)->VT:...def__iter__(self)->Iterator[KT]:...def__len__(self)->int:...items:MyMap[str,int]# OK# This is a non-generic subclass of dictclassStrDict(dict[str,str]):def__str__(self)->str:returnf'StrDict({super().__str__()})'data:StrDict[int,int]# Error! StrDict is not genericdata2:StrDict# OK# This is a user-defined generic classclassReceiver(Generic[T]):defaccept(self,value:T)->None:...# This is a generic subclass of ReceiverclassAdvancedReceiver(Receiver[T]):...

Note

You have to add an explicitMapping base classif you want mypy to consider a user-defined class as a mapping (andSequence for sequences, etc.). This is becausemypy doesn’t usestructural subtyping for these ABCs, unlike simpler protocolslikeIterable, which usestructural subtyping.

When using the legacy syntax,Generic can be omittedfrom bases if there areother base classes that include type variables, such asMapping[KT,VT]in the above example. If you includeGeneric[...] in bases, thenit should list all type variables present in other bases (or more,if needed). The order of type parameters is defined by the followingrules:

  • IfGeneric[...] is present, then the order of parameters isalways determined by their order inGeneric[...].

  • If there are noGeneric[...] in bases, then all type parametersare collected in the lexicographic order (i.e. by first appearance).

Example:

fromtypingimportGeneric,TypeVar,AnyT=TypeVar('T')S=TypeVar('S')U=TypeVar('U')classOne(Generic[T]):...classAnother(Generic[T]):...classFirst(One[T],Another[S]):...classSecond(One[T],Another[S],Generic[S,U,T]):...x:First[int,str]# Here T is bound to int, S is bound to stry:Second[int,str,Any]# Here T is Any, S is int, and U is str

When using the Python 3.12 syntax, all type parameters must always beexplicitly defined immediately after the class name within[...], and theGeneric[...] base class is never used.

Generic functions

Functions can also be generic, i.e. they can have type parameters (Python 3.12 syntax):

fromcollections.abcimportSequence# A generic function!deffirst[T](seq:Sequence[T])->T:returnseq[0]

Here is the same example using the legacy syntax (Python 3.11 and earlier):

fromtypingimportTypeVar,SequenceT=TypeVar('T')# A generic function!deffirst(seq:Sequence[T])->T:returnseq[0]

As with generic classes, the type parameterT can be replaced with anytype. That meansfirst can be passed an argument with any sequence type,and the return type is derived from the sequence item type. Example:

reveal_type(first([1,2,3]))# Revealed type is "builtins.int"reveal_type(first(('a','b')))# Revealed type is "builtins.str"

When using the legacy syntax, a single definition of a type variable(such asT above) can be used in multiple generic functions orclasses. In this example we use the same type variable in two genericfunctions to declare type parameters:

fromtypingimportTypeVar,SequenceT=TypeVar('T')# Define type variabledeffirst(seq:Sequence[T])->T:returnseq[0]deflast(seq:Sequence[T])->T:returnseq[-1]

Since the Python 3.12 syntax is more concise, it doesn’t need (or have)an equivalent way of sharing type parameter definitions.

A variable cannot have a type variable in its type unless the typevariable is bound in a containing generic class or function.

When calling a generic function, you can’t explicitly pass the values oftype parameters as type arguments. The values of type parameters are alwaysinferred by mypy. This is not valid:

first[int]([1,2])# Error: can't use [...] with generic function

If you really need this, you can define a generic class with a__call__method.

Type variables with upper bounds

A type variable can also be restricted to having values that aresubtypes of a specific type. This type is called the upper bound ofthe type variable, and it is specified usingT:<bound> when using thePython 3.12 syntax. In the definition of a generic function or a genericclass that uses such a type variableT, the type represented byTis assumed to be a subtype of its upper bound, so you can use methodsof the upper bound on values of typeT (Python 3.12 syntax):

fromtypingimportSupportsAbsdefmax_by_abs[T:SupportsAbs[float]](*xs:T)->T:# We can use abs(), because T is a subtype of SupportsAbs[float].returnmax(xs,key=abs)

An upper bound can also be specified with thebound=... keywordargument toTypeVar.Here is the example using the legacy syntax (Python 3.11 and earlier):

fromtypingimportTypeVar,SupportsAbsT=TypeVar('T',bound=SupportsAbs[float])defmax_by_abs(*xs:T)->T:returnmax(xs,key=abs)

In a call to such a function, the typeT must be replaced by atype that is a subtype of its upper bound. Continuing the exampleabove:

max_by_abs(-3.5,2)# Okay, has type 'float'max_by_abs(5+6j,7)# Okay, has type 'complex'max_by_abs('a','b')# Error: 'str' is not a subtype of SupportsAbs[float]

Type parameters of generic classes may also have upper bounds, whichrestrict the valid values for the type parameter in the same way.

Generic methods and generic self

You can also define generic methods. Inparticular, theself parameter may also be generic, allowing amethod to return the most precise type known at the point of access.In this way, for example, you can type check a chain of settermethods (Python 3.12 syntax):

classShape:defset_scale[T:Shape](self:T,scale:float)->T:self.scale=scalereturnselfclassCircle(Shape):defset_radius(self,r:float)->'Circle':self.radius=rreturnselfclassSquare(Shape):defset_width(self,w:float)->'Square':self.width=wreturnselfcircle:Circle=Circle().set_scale(0.5).set_radius(2.7)square:Square=Square().set_scale(0.5).set_width(3.2)

Without using genericself, the last two lines could not be typechecked properly, since the return type ofset_scale would beShape, which doesn’t defineset_radius orset_width.

When using the legacy syntax, just use a type variable in themethod signature that is different from class type parameters (if anyare defined). Here is the above example using the legacysyntax (3.11 and earlier):

fromtypingimportTypeVarT=TypeVar('T',bound='Shape')classShape:defset_scale(self:T,scale:float)->T:self.scale=scalereturnselfclassCircle(Shape):defset_radius(self,r:float)->'Circle':self.radius=rreturnselfclassSquare(Shape):defset_width(self,w:float)->'Square':self.width=wreturnselfcircle:Circle=Circle().set_scale(0.5).set_radius(2.7)square:Square=Square().set_scale(0.5).set_width(3.2)

Other uses include factory methods, such as copy and deserialization methods.For class methods, you can also define genericcls, usingtype[T]orType[T] (Python 3.12 syntax):

classFriend:other:"Friend | None"=None@classmethoddefmake_pair[T:Friend](cls:type[T])->tuple[T,T]:a,b=cls(),cls()a.other=bb.other=areturna,bclassSuperFriend(Friend):passa,b=SuperFriend.make_pair()

Here is the same example using the legacy syntax (3.11 and earlier):

fromtypingimportTypeVarT=TypeVar('T',bound='Friend')classFriend:other:"Friend | None"=None@classmethoddefmake_pair(cls:type[T])->tuple[T,T]:a,b=cls(),cls()a.other=bb.other=areturna,bclassSuperFriend(Friend):passa,b=SuperFriend.make_pair()

Note that when overriding a method with genericself, you must eitherreturn a genericself too, or return an instance of the current class.In the latter case, you must implement this method in all future subclasses.

Note also that mypy cannot always verify that the implementation of a copyor a deserialization method returns the actual type of self. Thereforeyou may need to silence mypy inside these methods (but not at the call site),possibly by making use of theAny type or a#type:ignore comment.

Mypy lets you use generic self types in certain unsafe waysin order to support common idioms. For example, using a genericself type in an argument type is accepted even though it’s unsafe (Python 3.12syntax):

classBase:defcompare[T:Base](self:T,other:T)->bool:returnFalseclassSub(Base):def__init__(self,x:int)->None:self.x=x# This is unsafe (see below) but allowed because it's# a common pattern and rarely causes issues in practice.defcompare(self,other:'Sub')->bool:returnself.x>other.xb:Base=Sub(42)b.compare(Base())# Runtime error here: 'Base' object has no attribute 'x'

For some advanced uses of self types, seeadditional examples.

Automatic self types using typing.Self

Since the patterns described above are quite common, mypy supports asimpler syntax, introduced inPEP 673, to make them easier to use.Instead of introducing a type parameter and using an explicit annotationforself, you can import the special typetyping.Self that isautomatically transformed into a method-level type parameter with thecurrent class as the upper bound, and you don’t need an annotation forself (orcls in class methods). The example from the previoussection can be made simpler by usingSelf:

fromtypingimportSelfclassFriend:other:Self|None=None@classmethoddefmake_pair(cls)->tuple[Self,Self]:a,b=cls(),cls()a.other=bb.other=areturna,bclassSuperFriend(Friend):passa,b=SuperFriend.make_pair()

This is more compact than using explicit type parameters. Also, you canuseSelf in attribute annotations in addition to methods.

Note

To use this feature on Python versions earlier than 3.11, you will need toimportSelf fromtyping_extensions (version 4.0 or newer).

Variance of generic types

There are three main kinds of generic types with respect to subtyperelations between them: invariant, covariant, and contravariant.Assuming that we have a pair of typesA andB, andB isa subtype ofA, these are defined as follows:

  • A generic classMyCovGen[T] is called covariant in type variableT ifMyCovGen[B] is always a subtype ofMyCovGen[A].

  • A generic classMyContraGen[T] is called contravariant in typevariableT ifMyContraGen[A] is always a subtype ofMyContraGen[B].

  • A generic classMyInvGen[T] is called invariant inT if neitherof the above is true.

Let us illustrate this by few simple examples:

# We'll use these classes in the examples belowclassShape:...classTriangle(Shape):...classSquare(Shape):...
  • Most immutable container types, such asSequenceandfrozenset are covariant. Union types arealso covariant in all union items:Triangle|int isa subtype ofShape|int.

    defcount_lines(shapes:Sequence[Shape])->int:returnsum(shape.num_sidesforshapeinshapes)triangles:Sequence[Triangle]count_lines(triangles)# OKdeffoo(triangle:Triangle,num:int)->None:shape_or_number:Union[Shape,int]# a Triangle is a Shape, and a Shape is a valid Union[Shape, int]shape_or_number=triangle

    Covariance should feel relatively intuitive, but contravariance and invariancecan be harder to reason about.

  • Callable is an example of type that behaves contravariantin types of arguments. That is,Callable[[Shape],int] is a subtype ofCallable[[Triangle],int], despiteShape being a supertype ofTriangle. To understand this, consider:

    defcost_of_paint_required(triangle:Triangle,area_calculator:Callable[[Triangle],float])->float:returnarea_calculator(triangle)*DOLLAR_PER_SQ_FT# This straightforwardly worksdefarea_of_triangle(triangle:Triangle)->float:...cost_of_paint_required(triangle,area_of_triangle)# OK# But this works as well!defarea_of_any_shape(shape:Shape)->float:...cost_of_paint_required(triangle,area_of_any_shape)# OK

    cost_of_paint_required needs a callable that can calculate the area of atriangle. If we give it a callable that can calculate the area of anarbitrary shape (not just triangles), everything still works.

  • list is an invariant generic type. Naively, one would thinkthat it is covariant, likeSequence above, but consider this code:

    classCircle(Shape):# The rotate method is only defined on Circle, not on Shapedefrotate(self):...defadd_one(things:list[Shape])->None:things.append(Shape())my_circles:list[Circle]=[]add_one(my_circles)# This may appear safe, but...my_circles[0].rotate()# ...this will fail, since my_circles[0] is now a Shape, not a Circle

    Another example of invariant type isdict. Most mutable containersare invariant.

When using the Python 3.12 syntax for generics, mypy will automaticallyinfer the most flexible variance for each class type variable. HereBox will be inferred as covariant:

classBox[T]:# this type is implicitly covariantdef__init__(self,content:T)->None:self._content=contentdefget_content(self)->T:returnself._contentdeflook_into(box:Box[Shape]):...my_box=Box(Square())look_into(my_box)# OK, but mypy would complain here for an invariant type

Here the underscore prefix for_content is significant. Without anunderscore prefix, the class would be invariant, as the attribute wouldbe understood as a public, mutable attribute (a single underscore prefixhas no special significance for mypy in most other contexts). By declaringthe attribute asFinal, the class could still be made covariant:

fromtypingimportFinalclassBox[T]:# this type is implicitly covariantdef__init__(self,content:T)->None:self.content:Final=contentdefget_content(self)->T:returnself.content

When using the legacy syntax, mypy assumes that all user-defined genericsare invariant by default. To declare a given generic class as covariant orcontravariant, use type variables defined with special keyword argumentscovariant orcontravariant. For example (Python 3.11 or earlier):

fromtypingimportGeneric,TypeVarT_co=TypeVar('T_co',covariant=True)classBox(Generic[T_co]):# this type is declared covariantdef__init__(self,content:T_co)->None:self._content=contentdefget_content(self)->T_co:returnself._contentdeflook_into(box:Box[Shape]):...my_box=Box(Square())look_into(my_box)# OK, but mypy would complain here for an invariant type

Type variables with value restriction

By default, a type variable can be replaced with any type – or any type thatis a subtype of the upper bound, which defaults toobject. However, sometimesit’s useful to have a type variable that can only have some specific typesas its value. A typical example is a type variable that can only have valuesstr andbytes. This lets us define a function that can concatenatetwo strings or bytes objects, but it can’t be called with other argumenttypes (Python 3.12 syntax):

defconcat[S:(str,bytes)](x:S,y:S)->S:returnx+yconcat('a','b')# Okayconcat(b'a',b'b')# Okayconcat(1,2)# Error!

The same thing is also possibly using the legacy syntax (Python 3.11 or earlier):

fromtypingimportTypeVarAnyStr=TypeVar('AnyStr',str,bytes)defconcat(x:AnyStr,y:AnyStr)->AnyStr:returnx+y

No matter which syntax you use, such a type variable is called a type variablewith a value restriction. Importantly, this is different from a union type,since combinations ofstr andbytes are not accepted:

concat('string',b'bytes')# Error!

In this case, this is exactly what we want, since it’s not possibleto concatenate a string and a bytes object! If we tried to usea union type, the type checker would complain about this possibility:

defunion_concat(x:str|bytes,y:str|bytes)->str|bytes:returnx+y# Error: can't concatenate str and bytes

Another interesting special case is callingconcat() with asubtype ofstr:

classS(str):passss=concat(S('foo'),S('bar'))reveal_type(ss)# Revealed type is "builtins.str"

You may expect that the type ofss isS, but the type isactuallystr: a subtype gets promoted to one of the valid valuesfor the type variable, which in this case isstr.

This is thus subtly different from usingstr|bytes as an upper bound,where the return type would beS (seeType variables with upper bounds).Using a value restriction is correct forconcat, sinceconcatactually returns astr instance in the above example:

>>>print(type(ss))<class 'str'>

You can also use type variables with a restricted set of possiblevalues when defining a generic class. For example, the typePattern[S] is used for the returnvalue ofre.compile(), whereS can be eitherstrorbytes. Regular expressions can be based on a string or abytes pattern.

A type variable may not have both a value restriction and an upper bound.

Note that you may come acrossAnyStr imported fromtyping. This feature is now deprecated, but it means the sameas our definition ofAnyStr above.

Declaring decorators

Decorators are typically functions that take a function as an argument andreturn another function. Describing this behaviour in terms of types canbe a little tricky; we’ll show how you can use type variables and a specialkind of type variable called aparameter specification to do so.

Suppose we have the following decorator, not type annotated yet,that preserves the original function’s signature and merely prints the decoratedfunction’s name:

defprinting_decorator(func):defwrapper(*args,**kwds):print("Calling",func)returnfunc(*args,**kwds)returnwrapper

We can use it to decorate functionadd_forty_two:

# A decorated function.@printing_decoratordefadd_forty_two(value:int)->int:returnvalue+42a=add_forty_two(3)

Sinceprinting_decorator is not type-annotated, the following won’t get type checked:

reveal_type(a)# Revealed type is "Any"add_forty_two('foo')# No type checker error :(

This is a sorry state of affairs! If you run with--strict, mypy willeven alert you to this fact:Untypeddecoratormakesfunction"add_forty_two"untyped

Note that class decorators are handled differently than function decorators inmypy: decorating a class does not erase its type, even if the decorator hasincomplete type annotations.

Here’s how one could annotate the decorator (Python 3.12 syntax):

fromcollections.abcimportCallablefromtypingimportAny,cast# A decorator that preserves the signature.defprinting_decorator[F:Callable[...,Any]](func:F)->F:defwrapper(*args,**kwds):print("Calling",func)returnfunc(*args,**kwds)returncast(F,wrapper)@printing_decoratordefadd_forty_two(value:int)->int:returnvalue+42a=add_forty_two(3)reveal_type(a)# Revealed type is "builtins.int"add_forty_two('x')# Argument 1 to "add_forty_two" has incompatible type "str"; expected "int"

Here is the example using the legacy syntax (Python 3.11 and earlier):

fromcollections.abcimportCallablefromtypingimportAny,TypeVar,castF=TypeVar('F',bound=Callable[...,Any])# A decorator that preserves the signature.defprinting_decorator(func:F)->F:defwrapper(*args,**kwds):print("Calling",func)returnfunc(*args,**kwds)returncast(F,wrapper)@printing_decoratordefadd_forty_two(value:int)->int:returnvalue+42a=add_forty_two(3)reveal_type(a)# Revealed type is "builtins.int"add_forty_two('x')# Argument 1 to "add_forty_two" has incompatible type "str"; expected "int"

This still has some shortcomings. First, we need to use the unsafecast() to convince mypy thatwrapper() has the samesignature asfunc (seecasts).

Second, thewrapper() function is not tightly type checked, althoughwrapper functions are typically small enough that this is not a bigproblem. This is also the reason for thecast() call in thereturn statement inprinting_decorator().

However, we can use a parameter specification, introduced using**P,for a more faithful type annotation (Python 3.12 syntax):

fromcollections.abcimportCallabledefprinting_decorator[**P,T](func:Callable[P,T])->Callable[P,T]:defwrapper(*args:P.args,**kwds:P.kwargs)->T:print("Calling",func)returnfunc(*args,**kwds)returnwrapper

The same is possible using the legacy syntax withParamSpec(Python 3.11 and earlier):

fromcollections.abcimportCallablefromtypingimportTypeVarfromtyping_extensionsimportParamSpecP=ParamSpec('P')T=TypeVar('T')defprinting_decorator(func:Callable[P,T])->Callable[P,T]:defwrapper(*args:P.args,**kwds:P.kwargs)->T:print("Calling",func)returnfunc(*args,**kwds)returnwrapper

Parameter specifications also allow you to describe decorators thatalter the signature of the input function (Python 3.12 syntax):

fromcollections.abcimportCallable# We reuse 'P' in the return type, but replace 'T' with 'str'defstringify[**P,T](func:Callable[P,T])->Callable[P,str]:defwrapper(*args:P.args,**kwds:P.kwargs)->str:returnstr(func(*args,**kwds))returnwrapper@stringifydefadd_forty_two(value:int)->int:returnvalue+42a=add_forty_two(3)reveal_type(a)# Revealed type is "builtins.str"add_forty_two('x')# error: Argument 1 to "add_forty_two" has incompatible type "str"; expected "int"

Here is the above example using the legacy syntax (Python 3.11 and earlier):

fromcollections.abcimportCallablefromtypingimportTypeVarfromtyping_extensionsimportParamSpecP=ParamSpec('P')T=TypeVar('T')# We reuse 'P' in the return type, but replace 'T' with 'str'defstringify(func:Callable[P,T])->Callable[P,str]:defwrapper(*args:P.args,**kwds:P.kwargs)->str:returnstr(func(*args,**kwds))returnwrapper

You can also insert an argument in a decorator (Python 3.12 syntax):

fromcollections.abcimportCallablefromtypingimportConcatenatedefprinting_decorator[**P,T](func:Callable[P,T])->Callable[Concatenate[str,P],T]:defwrapper(msg:str,/,*args:P.args,**kwds:P.kwargs)->T:print("Calling",func,"with",msg)returnfunc(*args,**kwds)returnwrapper@printing_decoratordefadd_forty_two(value:int)->int:returnvalue+42a=add_forty_two('three',3)

Here is the same function using the legacy syntax (Python 3.11 and earlier):

fromcollections.abcimportCallablefromtypingimportTypeVarfromtyping_extensionsimportConcatenate,ParamSpecP=ParamSpec('P')T=TypeVar('T')defprinting_decorator(func:Callable[P,T])->Callable[Concatenate[str,P],T]:defwrapper(msg:str,/,*args:P.args,**kwds:P.kwargs)->T:print("Calling",func,"with",msg)returnfunc(*args,**kwds)returnwrapper

Decorator factories

Functions that take arguments and return a decorator (also called second-order decorators), aresimilarly supported via generics (Python 3.12 syntax):

fromcollections.abcimportCallablefromtypingimportAnydefroute[F:Callable[...,Any]](url:str)->Callable[[F],F]:...@route(url='/')defindex(request:Any)->str:return'Hello world'

Note that mypy infers thatF is used to make theCallable return valueofroute generic, instead of makingroute itself generic, sinceF isonly used in the return type. Python has no explicit syntax to mark thatFis only bound in the return value.

Here is the example using the legacy syntax (Python 3.11 and earlier):

fromcollections.abcimportCallablefromtypingimportAny,TypeVarF=TypeVar('F',bound=Callable[...,Any])defroute(url:str)->Callable[[F],F]:...@route(url='/')defindex(request:Any)->str:return'Hello world'

Sometimes the same decorator supports both bare calls and calls with arguments. This can beachieved by combining with@overload (Python 3.12 syntax):

fromcollections.abcimportCallablefromtypingimportAny,overload# Bare decorator usage@overloaddefatomic[F:Callable[...,Any]](func:F,/)->F:...# Decorator with arguments@overloaddefatomic[F:Callable[...,Any]](*,savepoint:bool=True)->Callable[[F],F]:...# Implementationdefatomic(func:Callable[...,Any]|None=None,/,*,savepoint:bool=True):defdecorator(func:Callable[...,Any]):...# Code goes hereif__funcisnotNone:returndecorator(__func)else:returndecorator# Usage@atomicdeffunc1()->None:...@atomic(savepoint=False)deffunc2()->None:...

Here is the decorator from the example using the legacy syntax(Python 3.11 and earlier):

fromcollections.abcimportCallablefromtypingimportAny,Optional,TypeVar,overloadF=TypeVar('F',bound=Callable[...,Any])# Bare decorator usage@overloaddefatomic(func:F,/)->F:...# Decorator with arguments@overloaddefatomic(*,savepoint:bool=True)->Callable[[F],F]:...# Implementationdefatomic(func:Optional[Callable[...,Any]]=None,/,*,savepoint:bool=True):...# Same as above

Generic protocols

Mypy supports generic protocols (see alsoProtocols and structural subtyping). Severalpredefined protocols are generic, such asIterable[T], and you can define additionalgeneric protocols. Generic protocols mostly follow the normal rules forgeneric classes. Example (Python 3.12 syntax):

fromtypingimportProtocolclassBox[T](Protocol):content:Tdefdo_stuff(one:Box[str],other:Box[bytes])->None:...classStringWrapper:def__init__(self,content:str)->None:self.content=contentclassBytesWrapper:def__init__(self,content:bytes)->None:self.content=contentdo_stuff(StringWrapper('one'),BytesWrapper(b'other'))# OKx:Box[float]=...y:Box[int]=...x=y# Error -- Box is invariant

Here is the definition ofBox from the above example using the legacysyntax (Python 3.11 and earlier):

fromtypingimportProtocol,TypeVarT=TypeVar('T')classBox(Protocol[T]):content:T

Note thatclassClassName(Protocol[T]) is allowed as a shorthand forclassClassName(Protocol,Generic[T]) when using the legacy syntax,as perPEP 544: Generic protocols.This form is only valid when using the legacy syntax.

When using the legacy syntax, there is an important difference betweengeneric protocols and ordinary generic classes: mypy checks that thedeclared variances of generic type variables in a protocol match howthey are used in the protocol definition. The protocol in this exampleis rejected, since the type variableT is used covariantly asa return type, but the type variable is invariant:

fromtypingimportProtocol,TypeVarT=TypeVar('T')classReadOnlyBox(Protocol[T]):# error: Invariant type variable "T" used in protocol where covariant one is expecteddefcontent(self)->T:...

This example correctly uses a covariant type variable:

fromtypingimportProtocol,TypeVarT_co=TypeVar('T_co',covariant=True)classReadOnlyBox(Protocol[T_co]):# OKdefcontent(self)->T_co:...ax:ReadOnlyBox[float]=...ay:ReadOnlyBox[int]=...ax=ay# OK -- ReadOnlyBox is covariant

SeeVariance of generic types for more about variance.

Generic protocols can also be recursive. Example (Python 3.12 syntax):

classLinked[T](Protocol):val:Tdefnext(self)->'Linked[T]':...classL:val:intdefnext(self)->'L':...deflast(seq:Linked[T])->T:...result=last(L())reveal_type(result)# Revealed type is "builtins.int"

Here is the definition ofLinked using the legacy syntax(Python 3.11 and earlier):

fromtypingimportTypeVarT=TypeVar('T')classLinked(Protocol[T]):val:Tdefnext(self)->'Linked[T]':...

Generic type aliases

Type aliases can be generic. In this case they can be used in two ways.First, subscripted aliases are equivalent to original types with substituted typevariables. Second, unsubscripted aliases are treated as original types with typeparameters replaced withAny.

Thetype statement introduced in Python 3.12 is used to define generictype aliases (it also supports non-generic type aliases):

fromcollections.abcimportCallable,IterabletypeTInt[S]=tuple[int,S]typeUInt[S]=S|inttypeCBack[S]=Callable[...,S]defresponse(query:str)->UInt[str]:# Same as str | int...defactivate[S](cb:CBack[S])->S:# Same as Callable[..., S]...table_entry:TInt# Same as tuple[int, Any]typeVec[T:(int,float,complex)]=Iterable[tuple[T,T]]definproduct[T:(int,float,complex)](v:Vec[T])->T:returnsum(x*yforx,yinv)defdilate[T:(int,float,complex)](v:Vec[T],scale:T)->Vec[T]:return((x*scale,y*scale)forx,yinv)v1:Vec[int]=[]# Same as Iterable[tuple[int, int]]v2:Vec=[]# Same as Iterable[tuple[Any, Any]]v3:Vec[int,int]=[]# Error: Invalid alias, too many type arguments!

There is also a legacy syntax that relies onTypeVar.Here the number of type arguments must match the number of free type variablesin the generic type alias definition. A type variables is free if it’s nota type parameter of a surrounding class or function. Example (followingPEP 484: Type aliases, Python 3.11 and earlier):

fromtypingimportTypeVar,Iterable,Union,CallableS=TypeVar('S')TInt=tuple[int,S]# 1 type parameter, since only S is freeUInt=Union[S,int]CBack=Callable[...,S]defresponse(query:str)->UInt[str]:# Same as Union[str, int]...defactivate(cb:CBack[S])->S:# Same as Callable[..., S]...table_entry:TInt# Same as tuple[int, Any]T=TypeVar('T',int,float,complex)Vec=Iterable[tuple[T,T]]definproduct(v:Vec[T])->T:returnsum(x*yforx,yinv)defdilate(v:Vec[T],scale:T)->Vec[T]:return((x*scale,y*scale)forx,yinv)v1:Vec[int]=[]# Same as Iterable[tuple[int, int]]v2:Vec=[]# Same as Iterable[tuple[Any, Any]]v3:Vec[int,int]=[]# Error: Invalid alias, too many type arguments!

Type aliases can be imported from modules just like other names. Analias can also target another alias, although building complex chainsof aliases is not recommended – this impedes code readability, thusdefeating the purpose of using aliases. Example (Python 3.12 syntax):

fromexample1importAliasTypefromexample2importVec# AliasType and Vec are type aliases (Vec as defined above)deffun()->AliasType:...typeOIntVec=Vec[int]|None

Type aliases defined using thetype statement are not valid asbase classes, and they can’t be used to construct instances:

fromexample1importAliasTypefromexample2importVec# AliasType and Vec are type aliases (Vec as defined above)classNewVec[T](Vec[T]):# Error: not valid as base class...x=AliasType()# Error: can't be used to create instances

Here are examples using the legacy syntax (Python 3.11 and earlier):

fromtypingimportTypeVar,Generic,Optionalfromexample1importAliasTypefromexample2importVec# AliasType and Vec are type aliases (Vec as defined above)deffun()->AliasType:...OIntVec=Optional[Vec[int]]T=TypeVar('T')# Old-style type aliases can be used as base classes and you can# construct instances using themclassNewVec(Vec[T]):...x=AliasType()fori,jinNewVec[int]():...

Using type variable bounds or value restriction in generic aliases hasthe same effect as in generic classes and functions.

Differences between the new and old syntax

There are a few notable differences between the new (Python 3.12 and later)and the old syntax for generic classes, functions and type aliases, beyondthe obvious syntactic differences:

  • Type variables defined using the old syntax create definitions at runtimein the surrounding namespace, whereas the type variables defined using thenew syntax are only defined within the class, function or type variablethat uses them.

  • Type variable definitions can be shared when using the old syntax, butthe new syntax doesn’t support this.

  • When using the new syntax, the variance of class type variables is alwaysinferred.

  • Type aliases defined using the new syntax can contain forward referencesand recursive references without using string literal escaping. Thesame is true for the bounds and constraints of type variables.

  • The new syntax lets you define a generic alias where the definition doesn’tcontain a reference to a type parameter. This is occasionally useful, atleast when conditionally defining type aliases.

  • Type aliases defined using the new syntax can’t be used as base classesand can’t be used to construct instances, unlike aliases defined using theold syntax.

Generic class internals

You may wonder what happens at runtime when you index a generic class.Indexing returns ageneric alias to the original class that returns instancesof the original class on instantiation (Python 3.12 syntax):

>>>classStack[T]:...>>>Stack__main__.Stack>>>Stack[int]__main__.Stack[int]>>>instance=Stack[int]()>>>instance.__class____main__.Stack

Here is the same example using the legacy syntax (Python 3.11 and earlier):

>>>fromtypingimportTypeVar,Generic>>>T=TypeVar('T')>>>classStack(Generic[T]):...>>>Stack__main__.Stack>>>Stack[int]__main__.Stack[int]>>>instance=Stack[int]()>>>instance.__class____main__.Stack

Generic aliases can be instantiated or subclassed, similar to realclasses, but the above examples illustrate that type variables areerased at runtime. GenericStack instances are just ordinaryPython objects, and they have no extra runtime overhead or magic dueto being generic, other than theGeneric base class that overloadsthe indexing operator using__class_getitem__.typing.Genericis included as an implicit base class even when using the new syntax:

>>>classStack[T]:...>>>Stack.mro()[<class '__main__.Stack'>, <class 'typing.Generic'>, <class 'object'>]

Note that in Python 3.8 and earlier, the built-in typeslist,dict and others do not support indexing.This is why we have the aliasesList,Dict and so on in thetypingmodule. Indexing these aliases gives you a generic alias thatresembles generic aliases constructed by directly indexing the targetclass in more recent versions of Python:

>>># Only relevant for Python 3.8 and below>>># If using Python 3.9 or newer, prefer the 'list[int]' syntax>>>fromtypingimportList>>>List[int]typing.List[int]

Note that the generic aliases intyping don’t support constructinginstances, unlike the corresponding built-in classes:

>>>list[int]()[]>>>fromtypingimportList>>>List[int]()Traceback (most recent call last):...TypeError:Type List cannot be instantiated; use list() instead
On this page

[8]ページ先頭

©2009-2025 Movatter.jp