Type qualifiers

This chapter describes the behavior of sometype qualifiers.Additional type qualifiers are covered in other chapters:

@final

(Originally specified inPEP 591.)

Thetyping.final decorator is used to restrict the use ofinheritance and overriding.

A type checker should prohibit any class decorated with@finalfrom being subclassed and any method decorated with@final frombeing overridden in a subclass. The method decorator version may beused with all of instance methods, class methods, static methods, and properties.

For example:

fromtypingimportfinal@finalclassBase:...classDerived(Base):# Error: Cannot inherit from final class "Base"...

and:

fromtypingimportfinalclassBase:@finaldeffoo(self)->None:...classDerived(Base):deffoo(self)->None:# Error: Cannot override final attribute "foo"# (previously declared in base class "Base")...

For overloaded methods,@final should be placed on theimplementation (or on the first overload, for stubs):

fromtypingimportAny,overloadclassBase:@overloaddefmethod(self)->None:...@overloaddefmethod(self,arg:int)->int:...@finaldefmethod(self,x=None):...

It is an error to use@final on a non-method function.

Final

(Originally specified inPEP 591.)

Thetyping.Finaltype qualifier is used to indicate that avariable or attribute should not be reassigned, redefined, or overridden.

Syntax

Final may be used in one of several forms:

  • With an explicit type, using the syntaxFinal[<type>]. Example:

    ID:Final[float]=1
  • With no type annotation. Example:

    ID:Final=1

    The typechecker should apply its usual type inference mechanisms todetermine the type ofID (here, likely,int). Note that unlike forgeneric classes this isnot the same asFinal[Any].

  • In class bodies and stub files you can omit the right hand side and just writeID:Final[float]. If the right hand side is omitted, there mustbe an explicit type argument toFinal.

  • Finally, asself.id:Final=1 (also optionally with a type insquare brackets). This is allowedonly in__init__ methods, sothat the final instance attribute is assigned only once when aninstance is created.

Semantics and examples

The two main rules for defining a final name are:

  • There can beat most one final declaration per module or class fora given attribute. There can’t be separate class-level and instance-levelconstants with the same name.

  • There must beexactly one assignment to a final name.

This means a type checker should prevent further assignments to finalnames in type-checked code:

fromtypingimportFinalRATE:Final=3000classBase:DEFAULT_ID:Final=0RATE=300# Error: can't assign to final attributeBase.DEFAULT_ID=1# Error: can't override a final attribute

Note that a type checker need not allowFinal declarations inside loopssince the runtime will see multiple assignments to the same variable insubsequent iterations.

Additionally, a type checker should prevent final attributes frombeing overridden in a subclass:

fromtypingimportFinalclassWindow:BORDER_WIDTH:Final=2.5...classListView(Window):BORDER_WIDTH=3# Error: can't override a final attribute

A final attribute declared in a class body without an initializer mustbe initialized in the__init__ method (except in stub files):

classImmutablePoint:x:Final[int]y:Final[int]# Error: final attribute without an initializerdef__init__(self)->None:self.x=1# Good

The generated__init__ method ofDataclasses qualifies for thisrequirement: a barex:Final[int] is permitted in a dataclass body, becausethe generated__init__ will initializex.

Type checkers should infer a final attribute that is initialized in a classbody as being a class variable, except in the case ofDataclasses, wherex:Final[int]=3 creates a dataclass field and instance-level finalattributex with default value3;x:ClassVar[Final[int]]=3 isnecessary to create a final class variable with value3. Innon-dataclasses, combiningClassVar andFinal is redundant, and typecheckers may choose to warn or error on the redundancy.

Final may only be used in assignments or variable annotations. Using it inany other position is an error. In particular,Final can’t be used inannotations for function arguments:

x:list[Final[int]]=[]# Error!deffun(x:Final[List[int]])->None:# Error!...

Final may be wrapped only by other type qualifiers (e.g.ClassVar orAnnotated). It cannot be used in a type parameter (e.g.list[Final[int]] is not permitted.)

Note that declaring a name as final only guarantees that the name willnot be re-bound to another value, but does not make the valueimmutable. Immutable ABCs and containers may be used in combinationwithFinal to prevent mutating such values:

x:Final=['a','b']x.append('c')# OKy:Final[Sequence[str]]=['a','b']y.append('x')# Error: "Sequence[str]" has no attribute "append"z:Final=('a','b')# Also works

Type checkers should treat uses of a final name that was initializedwith a literal as if it was replaced by the literal. For example, thefollowing should be allowed:

fromtypingimportNamedTuple,FinalX:Final="x"Y:Final="y"N=NamedTuple("N",[(X,int),(Y,int)])

Final cannot be used as a qualifier for aTypedDictitem or aNamedTuple field. Such usage also generatesan error at runtime.

ImportingFinal Variables

If a module declares aFinal variable and another module imports thatvariable in an import statement by name or wildcard, the imported symbolinherits theFinal type qualifier. Any attempt to assign a different valueto this symbol should be flagged as an error by a type checker:

# lib/submodule.pyfromtypingimportFinalPI:Final=3.14# lib/__init__.pyfrom.submoduleimportPI# PI is Final# test1.pyfromlibimportPIPI=0# Error: Can't assign to Final valuefromlibimportPIasPI2PI2=0# Error: Can't assign to Final value# test2.pyfromlibimport*PI=0# Error: Can't assign to Final value

Annotated

(Originally specified byPEP 593.)

Syntax

Annotated is parameterized with abase expression and at least onePython value representing associatedmetadata:

fromtypingimportAnnotatedAnnotated[BaseExpr,Metadata1,Metadata2,...]

Here are the specific details of the syntax:

  • The base expression (the first argument toAnnotated) must be validin the context where it is being used:

    • IfAnnotated is used in a place where arbitraryannotation expressions are allowed,the base expression may be an annotation expression.

    • Otherwise, the base expression must be a validtype expression.

  • Multiple metadata elements are supported (Annotated supports variadicarguments):

    Annotated[int,ValueRange(3,10),ctype("char")]
  • There must be at least one metadata element (Annotated[int] is not valid)

  • The order of the metadata is preserved and matters for equalitychecks:

    Annotated[int,ValueRange(3,10),ctype("char")]!=Annotated[int,ctype("char"),ValueRange(3,10)]
  • NestedAnnotated types are flattened, with metadata orderedstarting with the innermostAnnotated expression:

    Annotated[Annotated[int,ValueRange(3,10)],ctype("char")]==Annotated[int,ValueRange(3,10),ctype("char")]
  • Duplicated metadata elements are not removed:

    Annotated[int,ValueRange(3,10)]!=Annotated[int,ValueRange(3,10),ValueRange(3,10)]
  • Annotated can be used in definition of nested and generic aliases,but only if it wraps atype expression:

    typeVec[T]=Annotated[list[tuple[T,T]],MaxLen(10)]typeV=Vec[int]# Annotated[list[tuple[int, int]], MaxLen(10)]
  • As with mostspecial forms,Annotated is not assignable totype ortype[T]:

    v1:type[int]=Annotated[int,""]# Type errorSmallInt:TypeAlias=Annotated[int,ValueRange(0,100)]v2:type[Any]=SmallInt# Type error
  • An attempt to callAnnotated (whether parameterized or not) should betreated as a type error by type checkers:

    Annotated()# Type errorAnnotated[int,""](0)# Type errorSmallInt=Annotated[int,ValueRange(0,100)]SmallInt(1)# Type error

PEP 593 and an earlier version of this specification used the term“annotations” instead of “metadata” for the extra arguments toAnnotated. The term “annotations” is deprecated to avoid confusionwith the parameter, return, and variable annotations that are part ofthe Python syntax.

Meaning

The metadata provided byAnnotated can be used for either staticor runtime analysis. If a library (or tool) encounters an instance ofAnnotated[T,x] and has no special logic for metadata elementx, itshould ignore it and treat the expression as equivalent toT. Thus, in general,anytype expression orannotation expression may bewrapped inAnnotated without changing the meaning of thewrapped expression. However, typecheckers may additionally choose to recognize particular metadata elements and usethem to implement extensions to the standard type system.

Annotated metadata may apply either to the base expression or to the symbolbeing annotated, or even to some other aspect of the program.

Consuming metadata

Ultimately, deciding how to interpret the metadata (ifat all) is the responsibility of the tool or library encountering theAnnotated type. A tool or library encountering anAnnotated typecan scan through the metadata to determine if they are of interest(e.g., usingisinstance()).

Unknown metadata: When a tool or a library does not supportmetadata or encounters an unknown metadata element, it should ignore itand treat the annotation as the base expression.

Namespacing metadata: Namespaces are not needed for metadata sincethe class of the metadata object acts as a namespace.

Multiple metadata elements: It’s up to the tool consuming the metadatato decide whether the client is allowed to have several metadata elements onone annotation and how to merge those elements.

Since theAnnotated type allows you to put several metadata elements ofthe same (or different) type(s) on any annotation, the tools or librariesconsuming the metadata are in charge of dealing with potentialduplicates. For example, if you are doing value range analysis you mightallow this:

T1=Annotated[int,ValueRange(-10,5)]T2=Annotated[T1,ValueRange(-20,3)]

Flattening nested annotations, this translates to:

T2=Annotated[int,ValueRange(-10,5),ValueRange(-20,3)]

Aliases & Concerns over verbosity

Writingtyping.Annotated everywhere can be quite verbose;fortunately, the ability to alias types means that in practice wedon’t expect clients to have to write lots of boilerplate code:

typeConst[T]=Annotated[T,my_annotations.CONST]classC:defconst_method(self,x:Const[list[int]])->int:...