Protocols

(Originally specified inPEP 544.)

Terminology

The termprotocols is used for some types supportingstructuralsubtyping. The reason is that the termiterator protocol,for example, is widely understood in the community, and coming up witha new term for this concept in a statically typed context would just createconfusion.

This has the drawback that the termprotocol becomes overloaded withtwo subtly different meanings: the first is the traditional, well-known butslightly fuzzy concept of protocols such as iterator; the second is the moreexplicitly defined concept of protocols in statically typed code.The distinction is not important most of the time, and in othercases we can just add a qualifier such asprotocol classeswhen referring to the static type concept.

If a class includes a protocol in its MRO, the class is called anexplicitsubclass of the protocol. If a class defines all attributes and methods of aprotocol with types that areassignable to the types of the protocol’sattributes and methods, it is said to implement the protocol and to beassignable to the protocol. If a class is assignable to a protocol but theprotocol is not included in the MRO, the class isimplicitly assignable tothe protocol. (Note that one can explicitly subclass a protocol and still notimplement it if a protocol attribute is set toNone in the subclass. SeePythondata model for details.)

The attributes (variables and methods) of a protocol that are mandatory foranother class for it to be assignable to the protocol are called “protocolmembers”.

Defining a protocol

Protocols are defined by including aspecial formtyping.Protocol(an instance ofabc.ABCMeta) in the base classes list, typicallyat the end of the list. Here is a simple example:

fromtypingimportProtocolclassSupportsClose(Protocol):defclose(self)->None:...

Now if one defines a classResource with aclose() method whose typesignature isassignable toSupportsClose.close, it would implicitlybe assignable toSupportsClose, sincestructural assignability isused for protocol types:

classResource:...defclose(self)->None:self.file.close()self.lock.release()

Apart from a few restrictions explicitly mentioned below, protocol types canbe used in every context where normal types can:

defclose_all(things:Iterable[SupportsClose])->None:fortinthings:t.close()f=open('foo.txt')r=Resource()close_all([f,r])# OK!close_all([1])# Error: 'int' has no 'close' method

Note that both the user-defined classResource and the built-inIO type(the return type ofopen()) are assignable toSupportsClose, becauseeach provides aclose() method with an assignable type signature.

Protocol members

All methods defined in the protocol class body are protocol members, bothnormal and decorated with@abstractmethod. If any parameters of aprotocol method are not annotated, then their types are assumed to beAny(see“The meaning of annotations”). Bodies of protocol methods are type checked.An abstract method that should not be called viasuper() ought to raiseNotImplementedError. Example:

fromtypingimportProtocolfromabcimportabstractmethodclassExample(Protocol):deffirst(self)->int:# This is a protocol memberreturn42@abstractmethoddefsecond(self)->int:# Method without a default implementationraiseNotImplementedError

Static methods, class methods, and properties are equally allowedin protocols.

To define a protocol variable, one can use variableannotations in the class body. Additional attributesonly defined inthe body of a method by assignment viaself are not allowed. The rationalefor this is that the protocol class implementation is often not shared bysubtypes, so the interface should not depend on the default implementation.Examples:

fromtypingimportProtocolclassTemplate(Protocol):name:str# This is a protocol membervalue:int=0# This one too (with default)defmethod(self)->None:self.temp:list[int]=[]# Error in type checkerclassConcrete:def__init__(self,name:str,value:int)->None:self.name=nameself.value=valuedefmethod(self)->None:returnvar:Template=Concrete('value',42)# OK

To distinguish between protocol class variables and protocol instancevariables, the specialClassVar annotation should be used.By default, protocol variables as defined above are consideredreadable and writable. To define a read-only protocol variable, one can usean (abstract) property.

Explicitly declaring implementation

To explicitly declare that a certain class implements a given protocol,it can be used as a regular base class. In this case a class could usedefault implementations of protocol members. Static analysis tools areexpected to automatically detect that a class implements a given protocol.So while it’s possible to subclass a protocol explicitly, it’snot necessaryto do so for the sake of type-checking.

The default implementations cannot be used if the assignable-to relationship isimplicit and onlystructural – the semantics of inheritance is notchanged. Examples:

classPColor(Protocol):@abstractmethoddefdraw(self)->str:...defcomplex_method(self)->int:# some complex code hereclassNiceColor(PColor):defdraw(self)->str:return"deep blue"classBadColor(PColor):defdraw(self)->str:returnsuper().draw()# Error, no default implementationclassImplicitColor:# Note no 'PColor' base heredefdraw(self)->str:return"probably gray"defcomplex_method(self)->int:# class needs to implement thisnice:NiceColoranother:ImplicitColordefrepresent(c:PColor)->None:print(c.draw(),c.complex_method())represent(nice)# OKrepresent(another)# Also OK

Note that there is little difference between explicitly subclassing andimplicitly implementing the protocol; the main benefit of explicit subclassingis to get some protocol methods “for free”. In addition, type checkers canstatically verify that the class actually implements the protocol correctly:

classRGB(Protocol):rgb:tuple[int,int,int]@abstractmethoddefintensity(self)->int:return0classPoint(RGB):def__init__(self,red:int,green:int,blue:str)->None:self.rgb=red,green,blue# Error, 'blue' must be 'int'# Type checker might warn that 'intensity' is not defined

A class can explicitly inherit from multiple protocols and also from normalclasses. In this case methods are resolved using normal MRO and a type checkerverifies that all member assignability is correct. The semantics of@abstractmethod is not changed; all of them must be implemented by anexplicit subclass before it can be instantiated.

Merging and extending protocols

The general philosophy is that protocols are mostly like regular ABCs, but astatic type checker will handle them specially. Subclassing a protocol classwould not turn the subclass into a protocol unless it also hastyping.Protocol as an explicit base class. Without this base, the class is“downgraded” to a regular ABC that cannot be used withstructuralsubtyping. The rationale for this rule is that we don’t want to accidentallyhave some class act as a protocol just because one of its base classes happensto be one. We still slightly prefernominal subtyping over structuralsubtyping in the static typing world.

A subprotocol can be defined by havingboth one or more protocols asimmediate base classes and also havingtyping.Protocol as an immediatebase class:

fromtypingimportProtocolfromcollections.abcimportSizedclassSizedAndClosable(Sized,Protocol):defclose(self)->None:...

Now the protocolSizedAndClosable is a protocol with two methods,__len__ andclose. If one omitsProtocol in the base class list,this would be a regular (non-protocol) class that must implementSized.Alternatively, one can implementSizedAndClosable protocol by mergingtheSupportsClose protocol from the example in theprotocol-definition sectionwithtyping.Sized:

fromcollections.abcimportSizedclassSupportsClose(Protocol):defclose(self)->None:...classSizedAndClosable(Sized,SupportsClose,Protocol):pass

The two definitions ofSizedAndClosable are equivalent. Subclassrelationships between protocols are not meaningful when consideringassignability, sincestructuralassignability isthe criterion, not the MRO.

IfProtocol is included in the base class list, all the other base classesmust be protocols. A protocol can’t extend a regular class.Note that rules around explicit subclassing are differentfrom regular ABCs, where abstractness is simply defined by having at least oneabstract method being unimplemented. Protocol classes must be markedexplicitly.

Generic protocols

Generic protocols are important. For example,SupportsAbs,IterableandIterator are generic protocols. They are defined similar to normalnon-protocol generic types:

classIterable[T](Protocol):@abstractmethoddef__iter__(self)->Iterator[T]:...

The older (pre-3.12) syntaxProtocol[T,S,...] remains available as ashorthand forProtocol,Generic[T,S,...]. It is an error to combine theshorthand withGeneric[T,S,...] or to mix it with the newclassIterable[T] form:

classIterable(Protocol[T],Generic[T]):# INVALID...classIterable[T](Protocol[T]):# INVALID...

When using the generics syntax introduced in Python 3.12, the variance oftype variables is inferred. When using the pre-3.12 generics syntax, variancemust be specified (unlessinfer_variance=True is used). Type checkers willwarn if the declared variance does not match the protocol definition. Examples:

T=TypeVar('T')T_co=TypeVar('T_co',covariant=True)T_contra=TypeVar('T_contra',contravariant=True)classBox(Protocol[T_co]):defcontent(self)->T_co:...box:Box[float]second_box:Box[int]box=second_box# This is OK due to the covariance of 'Box'.classSender(Protocol[T_contra]):defsend(self,data:T_contra)->int:...sender:Sender[float]new_sender:Sender[int]new_sender=sender# OK, 'Sender' is contravariant.classProto(Protocol[T]):attr:T# this class is invariant, since it has a mutable attributevar:Proto[float]another_var:Proto[int]var=another_var# Error! 'Proto[float]' is not assignable to 'Proto[int]'.

Note that unlike nominal classes, de facto covariant protocols cannot bedeclared as invariant, since this can break transitivity of subtyping.For example:

T=TypeVar('T')classAnotherBox(Protocol[T]):# Error, this protocol is covariant in T,defcontent(self)->T:# not invariant....

Recursive protocols

Recursive protocols are also supported. Forward references to the protocolclass names can begiven as strings. Recursiveprotocols are useful for representing self-referential data structureslike trees in an abstract fashion:

classTraversable(Protocol):defleaves(self)->Iterable['Traversable']:...

Note that for recursive protocols, a class is considered assignable tothe protocol in situations where the decision depends on itself.Continuing the previous example:

classSimpleTree:defleaves(self)->list['SimpleTree']:...root:Traversable=SimpleTree()# OKclassTree(Generic[T]):defleaves(self)->list['Tree[T]']:...defwalk(graph:Traversable)->None:...tree:Tree[float]=Tree()walk(tree)# OK, 'Tree[float]' is assignable to 'Traversable'

Self-types in protocols

The self-types in protocols follow therules for other methods. For example:

classCopyable(Protocol):defcopy[C:Copyable](self:C)->C:classOne:defcopy(self)->'One':...classOther:defcopy[T:Other](self:T)->T:...c:Copyablec=One()# OKc=Other()# Also OK

Assignability relationships with other types

Protocols cannot be instantiated, so there are no values whoseruntime type is a protocol. For variables and parameters with protocol types,assignability relationships are subject to the following rules:

  • A protocol is never assignable to a concrete type.

  • A concrete typeX is assignable to a protocolP if and only ifXimplements all protocol members ofP with assignable types. In otherwords,assignability with respect to a protocol isalwaysstructural.

  • A protocolP1 is assignable to another protocolP2 ifP1 definesall protocol members ofP2 with assignable types.

Generic protocol types follow the same rules of variance as non-protocoltypes. Protocol types can be used in all contexts where any other typescan be used, such as in unions,ClassVar, type variables bounds, etc.Generic protocols follow the rules for generic abstract classes, except forusing structural assignability instead of assignability defined byinheritance relationships.

Static type checkers will recognize protocol implementations, even if thecorresponding protocols arenot imported:

# file lib.pyfromcollections.abcimportSizedclassListLike[T](Sized,Protocol):defappend(self,x:T)->None:passdefpopulate(lst:ListLike[int])->None:...# file main.pyfromlibimportpopulate# Note that ListLike is NOT importedclassMockStack:def__len__(self)->int:return42defappend(self,x:int)->None:print(x)populate([1,2,3])# Passes type checkpopulate(MockStack())# Also OK

Unions and intersections of protocols

Unions of protocol classes behaves the same way as for non-protocolclasses. For example:

fromtypingimportProtocolclassExitable(Protocol):defexit(self)->int:...classQuittable(Protocol):defquit(self)->int|None:...deffinish(task:Exitable|Quittable)->int:...classDefaultJob:...defquit(self)->int:return0finish(DefaultJob())# OK

One can use multiple inheritance to define an intersection of protocols.Example:

fromcollections.abcimportIterable,HashableclassHashableFloats(Iterable[float],Hashable,Protocol):passdefcached_func(args:HashableFloats)->float:...cached_func((1,2,3))# OK, tuple is both hashable and iterable

type[] and class objects vs protocols

Variables and parameters annotated withtype[Proto] accept only concrete(non-protocol)consistent subtypes ofProto.The main reason for this is to allow instantiation of parameters with suchtypes. For example:

classProto(Protocol):@abstractmethoddefmeth(self)->int:...classConcrete:defmeth(self)->int:return42deffun(cls:type[Proto])->int:returncls().meth()# OKfun(Proto)# Errorfun(Concrete)# OK

The same rule applies to variables:

var:Type[Proto]var=Proto# Errorvar=Concrete# OKvar().meth()# OK

Assigning an ABC or a protocol class to a variable is allowed if it isnot explicitly typed, and such assignment creates a type alias.For normal (non-abstract) classes, the behavior oftype[] isnot changed.

A class object is considered an implementation of a protocol if accessingall members on it results in types assignable to the types of the protocol members.For example:

fromtypingimportAny,ProtocolclassProtoA(Protocol):defmeth(self,x:int)->int:...classProtoB(Protocol):defmeth(self,obj:Any,x:int)->int:...classC:defmeth(self,x:int)->int:...a:ProtoA=C# Type check error, signatures don't match!b:ProtoB=C# OK

NewType() and type aliases

Protocols are essentially anonymous. To emphasize this point, static typecheckers might refuse protocol classes insideNewType() to avoid anillusion that a distinct type is provided:

fromtypingimportNewType,Protocolfromcollections.abcimportIteratorclassId(Protocol):code:intsecrets:Iterator[bytes]UserId=NewType('UserId',Id)# Error, can't provide distinct type

In contrast, type aliases are fully supported, including generic typealiases:

fromcollections.abcimportReversible,Iterable,SizedclassSizedIterable[T](Iterable[T],Sized,Protocol):passtypeCompatReversible[T]=Reversible[T]|SizedIterable[T]

Modules as implementations of protocols

A module object is accepted where a protocol is expected if the publicinterface of the given module is assignable to the expected protocol.For example:

# file default_config.pytimeout=100one_flag=Trueother_flag=False# file main.pyimportdefault_configfromtypingimportProtocolclassOptions(Protocol):timeout:intone_flag:boolother_flag:booldefsetup(options:Options)->None:...setup(default_config)# OK

To determine assignability of module level functions, theself argumentof the corresponding protocol methods is dropped. For example:

# callbacks.pydefon_error(x:int)->None:...defon_success()->None:...# main.pyimportcallbacksfromtypingimportProtocolclassReporter(Protocol):defon_error(self,x:int)->None:...defon_success(self)->None:...rp:Reporter=callbacks# Passes type check

@runtime_checkable decorator and narrowing types byisinstance()

The default semantics is thatisinstance() andissubclass() failfor protocol types. This is in the spirit of duck typing – protocolsbasically would be used to model duck typing statically, not explicitlyat runtime.

However, it should be possible for protocol types to implement custominstance and class checks when this makes sense, similar to howIterableand other ABCs incollections.abc andtyping already do it,but this is limited to non-generic and unsubscripted generic protocols(Iterable is statically equivalent toIterable[Any]).Thetyping module will define a special@runtime_checkable class decoratorthat provides the same semantics for class and instance checks as forcollections.abc classes, essentially making them “runtime protocols”:

fromtypingimportruntime_checkable,Protocol@runtime_checkableclassSupportsClose(Protocol):defclose(self):...assertisinstance(open('some/file'),SupportsClose)

Note that instance checks are not 100% reliable statically, which is whythis behavior is opt-in.The most type checkers can do is to treatisinstance(obj,Iterator)roughly as a simpler way to writehasattr(x,'__iter__')andhasattr(x,'__next__'). To minimizethe risks for this feature, the following rules are applied.

Definitions:

  • Data and non-data protocols: A protocol is called a non-data protocolif it only contains methods as members (for exampleSized,Iterator, etc). A protocol that contains at least one non-method member(likex:int) is called a data protocol.

  • Unsafe overlap: A typeX is called unsafely overlapping with a protocolP, ifX is not assignable toP, but it is assignable to thetype-erased version ofP where all members have typeAny. Inaddition, if at least one element of a union unsafely overlaps with aprotocolP, then the whole union is unsafely overlapping withP.

Specification:

  • A protocol can be used as a second argument inisinstance() andissubclass() only if it is explicitly opt-in by@runtime_checkabledecorator. This requirement exists because protocol checks are not type safein case of dynamically set attributes, and because type checkers can only provethat anisinstance() check is safe only for a given class, not for all itssubclasses.

  • isinstance() can be used with both data and non-data protocols, whileissubclass() can be used only with non-data protocols. This restrictionexists because some data attributes can be set on an instance in constructorand this information is not always available on the class object.

  • Type checkers should reject anisinstance() orissubclass() call, ifthere is an unsafe overlap between the type of the first argument andthe protocol.

  • Type checkers should be able to select a correct element from a union aftera safeisinstance() orissubclass() call. For narrowing from non-uniontypes, type checkers can use their best judgement (this is intentionallyunspecified, since a precise specification would require intersection types).