Permalink
Cannot retrieve contributors at this time
1511 lines (1123 sloc)
52.6 KB
| PEP: 544 | |
| Title: Protocols: Structural subtyping (static duck typing) | |
| Version: $Revision$ | |
| Last-Modified: $Date$ | |
| Author: Ivan Levkivskyi <levkivskyi@gmail.com>, Jukka Lehtosalo <jukka.lehtosalo@iki.fi>, Łukasz Langa <lukasz@python.org> | |
| BDFL-Delegate: Guido van Rossum <guido@python.org> | |
| Discussions-To: Python-Dev <python-dev@python.org> | |
| Status: Accepted | |
| Type: Standards Track | |
| Content-Type: text/x-rst | |
| Created: 05-Mar-2017 | |
| Python-Version: 3.8 | |
| Resolution: https://mail.python.org/archives/list/typing-sig@python.org/message/FDO4KFYWYQEP3U2HVVBEBR3SXPHQSHYR/ | |
| Abstract | |
| ======== | |
| Type hints introduced in PEP 484 can be used to specify type metadata | |
| for static type checkers and other third party tools. However, PEP 484 | |
| only specifies the semantics of *nominal* subtyping. In this PEP we specify | |
| static and runtime semantics of protocol classes that will provide a support | |
| for *structural* subtyping (static duck typing). | |
| .. _rationale: | |
| Rationale and Goals | |
| =================== | |
| Currently, PEP 484 and the ``typing`` module [typing]_ define abstract | |
| base classes for several common Python protocols such as ``Iterable`` and | |
| ``Sized``. The problem with them is that a class has to be explicitly marked | |
| to support them, which is unpythonic and unlike what one would | |
| normally do in idiomatic dynamically typed Python code. For example, | |
| this conforms to PEP 484:: | |
| from typing import Sized, Iterable, Iterator | |
| class Bucket(Sized, Iterable[int]): | |
| ... | |
| def __len__(self) -> int: ... | |
| def __iter__(self) -> Iterator[int]: ... | |
| The same problem appears with user-defined ABCs: they must be explicitly | |
| subclassed or registered. This is particularly difficult to do with library | |
| types as the type objects may be hidden deep in the implementation | |
| of the library. Also, extensive use of ABCs might impose additional | |
| runtime costs. | |
| The intention of this PEP is to solve all these problems | |
| by allowing users to write the above code without explicit base classes in | |
| the class definition, allowing ``Bucket`` to be implicitly considered | |
| a subtype of both ``Sized`` and ``Iterable[int]`` by static type checkers | |
| using structural [wiki-structural]_ subtyping:: | |
| from typing import Iterator, Iterable | |
| class Bucket: | |
| ... | |
| def __len__(self) -> int: ... | |
| def __iter__(self) -> Iterator[int]: ... | |
| def collect(items: Iterable[int]) -> int: ... | |
| result: int = collect(Bucket()) # Passes type check | |
| Note that ABCs in ``typing`` module already provide structural behavior | |
| at runtime, ``isinstance(Bucket(), Iterable)`` returns ``True``. | |
| The main goal of this proposal is to support such behavior statically. | |
| The same functionality will be provided for user-defined protocols, as | |
| specified below. The above code with a protocol class matches common Python | |
| conventions much better. It is also automatically extensible and works | |
| with additional, unrelated classes that happen to implement | |
| the required protocol. | |
| Nominal vs structural subtyping | |
| ------------------------------- | |
| Structural subtyping is natural for Python programmers since it matches | |
| the runtime semantics of duck typing: an object that has certain properties | |
| is treated independently of its actual runtime class. | |
| However, as discussed in PEP 483, both nominal and structural | |
| subtyping have their strengths and weaknesses. Therefore, in this PEP we | |
| *do not propose* to replace the nominal subtyping described by PEP 484 with | |
| structural subtyping completely. Instead, protocol classes as specified in | |
| this PEP complement normal classes, and users are free to choose | |
| where to apply a particular solution. See section on `rejected`_ ideas at the | |
| end of this PEP for additional motivation. | |
| Non-goals | |
| --------- | |
| At runtime, protocol classes will be simple ABCs. There is no intent to | |
| provide sophisticated runtime instance and class checks against protocol | |
| classes. This would be difficult and error-prone and will contradict the logic | |
| of PEP 484. As well, following PEP 484 and PEP 526 we state that protocols are | |
| **completely optional**: | |
| * No runtime semantics will be imposed for variables or parameters annotated | |
| with a protocol class. | |
| * Any checks will be performed only by third-party type checkers and | |
| other tools. | |
| * Programmers are free to not use them even if they use type annotations. | |
| * There is no intent to make protocols non-optional in the future. | |
| To reiterate, providing complex runtime semantics for protocol classes | |
| is not a goal of this PEP, the main goal is to provide a support and standards | |
| for *static* structural subtyping. The possibility to use protocols | |
| in the runtime context as ABCs is rather a minor bonus that exists mostly | |
| to provide a seamless transition for projects that already use ABCs. | |
| Existing Approaches to Structural Subtyping | |
| =========================================== | |
| Before describing the actual specification, we review and comment on existing | |
| approaches related to structural subtyping in Python and other languages: | |
| * ``zope.interface`` [zope-interfaces]_ was one of the first widely used | |
| approaches to structural subtyping in Python. It is implemented by providing | |
| special classes to distinguish interface classes from normal classes, | |
| to mark interface attributes, and to explicitly declare implementation. | |
| For example:: | |
| from zope.interface import Interface, Attribute, implementer | |
| class IEmployee(Interface): | |
| name = Attribute("Name of employee") | |
| def do(work): | |
| """Do some work""" | |
| @implementer(IEmployee) | |
| class Employee: | |
| name = 'Anonymous' | |
| def do(self, work): | |
| return work.start() | |
| Zope interfaces support various contracts and constraints for interface | |
| classes. For example:: | |
| from zope.interface import invariant | |
| def required_contact(obj): | |
| if not (obj.email or obj.phone): | |
| raise Exception("At least one contact info is required") | |
| class IPerson(Interface): | |
| name = Attribute("Name") | |
| email = Attribute("Email Address") | |
| phone = Attribute("Phone Number") | |
| invariant(required_contact) | |
| Even more detailed invariants are supported. However, Zope interfaces rely | |
| entirely on runtime validation. Such focus on runtime properties goes | |
| beyond the scope of the current proposal, and static support for invariants | |
| might be difficult to implement. However, the idea of marking an interface | |
| class with a special base class is reasonable and easy to implement both | |
| statically and at runtime. | |
| * Python abstract base classes [abstract-classes]_ are the standard | |
| library tool to provide some functionality similar to structural subtyping. | |
| The drawback of this approach is the necessity to either subclass | |
| the abstract class or register an implementation explicitly:: | |
| from abc import ABC | |
| class MyTuple(ABC): | |
| pass | |
| MyTuple.register(tuple) | |
| assert issubclass(tuple, MyTuple) | |
| assert isinstance((), MyTuple) | |
| As mentioned in the `rationale`_, we want to avoid such necessity, especially | |
| in static context. However, in a runtime context, ABCs are good candidates for | |
| protocol classes and they are already used extensively in | |
| the ``typing`` module. | |
| * Abstract classes defined in ``collections.abc`` module [collections-abc]_ | |
| are slightly more advanced since they implement a custom | |
| ``__subclasshook__()`` method that allows runtime structural checks without | |
| explicit registration:: | |
| from collections.abc import Iterable | |
| class MyIterable: | |
| def __iter__(self): | |
| return [] | |
| assert isinstance(MyIterable(), Iterable) | |
| Such behavior seems to be a perfect fit for both runtime and static behavior | |
| of protocols. As discussed in `rationale`_, we propose to add static support | |
| for such behavior. In addition, to allow users to achieve such runtime | |
| behavior for *user-defined* protocols a special ``@runtime_checkable`` decorator | |
| will be provided, see detailed `discussion`_ below. | |
| * TypeScript [typescript]_ provides support for user-defined classes and | |
| interfaces. Explicit implementation declaration is not required and | |
| structural subtyping is verified statically. For example:: | |
| interface LabeledItem { | |
| label: string; | |
| size?: int; | |
| } | |
| function printLabel(obj: LabeledItem) { | |
| console.log(obj.label); | |
| } | |
| let myObj = {size: 10, label: "Size 10 Object"}; | |
| printLabel(myObj); | |
| Note that optional interface members are supported. Also, TypeScript | |
| prohibits redundant members in implementations. While the idea of | |
| optional members looks interesting, it would complicate this proposal and | |
| it is not clear how useful it will be. Therefore, it is proposed to postpone | |
| this; see `rejected`_ ideas. In general, the idea of static protocol | |
| checking without runtime implications looks reasonable, and basically | |
| this proposal follows the same line. | |
| * Go [golang]_ uses a more radical approach and makes interfaces the primary | |
| way to provide type information. Also, assignments are used to explicitly | |
| ensure implementation:: | |
| type SomeInterface interface { | |
| SomeMethod() ([]byte, error) | |
| } | |
| if _, ok := someval.(SomeInterface); ok { | |
| fmt.Printf("value implements some interface") | |
| } | |
| Both these ideas are questionable in the context of this proposal. See | |
| the section on `rejected`_ ideas. | |
| .. _specification: | |
| Specification | |
| ============= | |
| Terminology | |
| ----------- | |
| We propose to use the term *protocols* for types supporting structural | |
| subtyping. The reason is that the term *iterator protocol*, | |
| for example, is widely understood in the community, and coming up with | |
| a new term for this concept in a statically typed context would just create | |
| confusion. | |
| This has the drawback that the term *protocol* becomes overloaded with | |
| two subtly different meanings: the first is the traditional, well-known but | |
| slightly fuzzy concept of protocols such as iterator; the second is the more | |
| explicitly defined concept of protocols in statically typed code. | |
| The distinction is not important most of the time, and in other | |
| cases we propose to just add a qualifier such as *protocol classes* | |
| when referring to the static type concept. | |
| If a class includes a protocol in its MRO, the class is called | |
| an *explicit* subclass of the protocol. If a class is a structural subtype | |
| of a protocol, it is said to implement the protocol and to be compatible | |
| with a protocol. If a class is compatible with a protocol but the protocol | |
| is not included in the MRO, the class is an *implicit* subtype | |
| of the protocol. (Note that one can explicitly subclass a protocol and | |
| still not implement it if a protocol attribute is set to ``None`` | |
| in the subclass, see Python [data-model]_ for details.) | |
| The attributes (variables and methods) of a protocol that are mandatory | |
| for other class in order to be considered a structural subtype are called | |
| protocol members. | |
| .. _definition: | |
| Defining a protocol | |
| ------------------- | |
| Protocols are defined by including a special new class ``typing.Protocol`` | |
| (an instance of ``abc.ABCMeta``) in the base classes list, typically | |
| at the end of the list. Here is a simple example:: | |
| from typing import Protocol | |
| class SupportsClose(Protocol): | |
| def close(self) -> None: | |
| ... | |
| Now if one defines a class ``Resource`` with a ``close()`` method that has | |
| a compatible signature, it would implicitly be a subtype of | |
| ``SupportsClose``, since the structural subtyping is used for | |
| protocol types:: | |
| class Resource: | |
| ... | |
| def close(self) -> None: | |
| self.file.close() | |
| self.lock.release() | |
| Apart from few restrictions explicitly mentioned below, protocol types can | |
| be used in every context where a normal types can:: | |
| def close_all(things: Iterable[SupportsClose]) -> None: | |
| for t in things: | |
| 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 class ``Resource`` and the built-in | |
| ``IO`` type (the return type of ``open()``) are considered subtypes of | |
| ``SupportsClose``, because they provide a ``close()`` method with | |
| a compatible type signature. | |
| Protocol members | |
| ---------------- | |
| All methods defined in the protocol class body are protocol members, both | |
| normal and decorated with ``@abstractmethod``. If any parameters of a | |
| protocol method are not annotated, then their types are assumed to be ``Any`` | |
| (see PEP 484). Bodies of protocol methods are type checked. | |
| An abstract method that should not be called via ``super()`` ought to raise | |
| ``NotImplementedError``. Example:: | |
| from typing import Protocol | |
| from abc import abstractmethod | |
| class Example(Protocol): | |
| def first(self) -> int: # This is a protocol member | |
| return 42 | |
| @abstractmethod | |
| def second(self) -> int: # Method without a default implementation | |
| raise NotImplementedError | |
| Static methods, class methods, and properties are equally allowed | |
| in protocols. | |
| To define a protocol variable, one can use PEP 526 variable | |
| annotations in the class body. Additional attributes *only* defined in | |
| the body of a method by assignment via ``self`` are not allowed. The rationale | |
| for this is that the protocol class implementation is often not shared by | |
| subtypes, so the interface should not depend on the default implementation. | |
| Examples:: | |
| from typing import Protocol, List | |
| class Template(Protocol): | |
| name: str # This is a protocol member | |
| value: int = 0 # This one too (with default) | |
| def method(self) -> None: | |
| self.temp: List[int] = [] # Error in type checker | |
| class Concrete: | |
| def __init__(self, name: str, value: int) -> None: | |
| self.name = name | |
| self.value = value | |
| def method(self) -> None: | |
| return | |
| var: Template = Concrete('value', 42) # OK | |
| To distinguish between protocol class variables and protocol instance | |
| variables, the special ``ClassVar`` annotation should be used as specified | |
| by PEP 526. By default, protocol variables as defined above are considered | |
| readable and writable. To define a read-only protocol variable, one can use | |
| an (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 use | |
| default implementations of protocol members. Static analysis tools are | |
| expected to automatically detect that a class implements a given protocol. | |
| So while it's possible to subclass a protocol explicitly, it's *not necessary* | |
| to do so for the sake of type-checking. | |
| The default implementations cannot be used if | |
| the subtype relationship is implicit and only via structural | |
| subtyping -- the semantics of inheritance is not changed. Examples:: | |
| class PColor(Protocol): | |
| @abstractmethod | |
| def draw(self) -> str: | |
| ... | |
| def complex_method(self) -> int: | |
| # some complex code here | |
| class NiceColor(PColor): | |
| def draw(self) -> str: | |
| return "deep blue" | |
| class BadColor(PColor): | |
| def draw(self) -> str: | |
| return super().draw() # Error, no default implementation | |
| class ImplicitColor: # Note no 'PColor' base here | |
| def draw(self) -> str: | |
| return "probably gray" | |
| def complex_method(self) -> int: | |
| # class needs to implement this | |
| nice: NiceColor | |
| another: ImplicitColor | |
| def represent(c: PColor) -> None: | |
| print(c.draw(), c.complex_method()) | |
| represent(nice) # OK | |
| represent(another) # Also OK | |
| Note that there is little difference between explicit and implicit | |
| subtypes, the main benefit of explicit subclassing is to get some protocol | |
| methods "for free". In addition, type checkers can statically verify that | |
| the class actually implements the protocol correctly:: | |
| class RGB(Protocol): | |
| rgb: Tuple[int, int, int] | |
| @abstractmethod | |
| def intensity(self) -> int: | |
| return 0 | |
| class Point(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 normal | |
| classes. In this case methods are resolved using normal MRO and a type checker | |
| verifies that all subtyping are correct. The semantics of ``@abstractmethod`` | |
| is not changed, all of them must be implemented by an explicit subclass | |
| before it can be instantiated. | |
| Merging and extending protocols | |
| ------------------------------- | |
| The general philosophy is that protocols are mostly like regular ABCs, | |
| but a static type checker will handle them specially. Subclassing a protocol | |
| class would not turn the subclass into a protocol unless it also has | |
| ``typing.Protocol`` as an explicit base class. Without this base, the class | |
| is "downgraded" to a regular ABC that cannot be used with structural | |
| subtyping. The rationale for this rule is that we don't want to accidentally | |
| have some class act as a protocol just because one of its base classes | |
| happens to be one. We still slightly prefer nominal subtyping over structural | |
| subtyping in the static typing world. | |
| A subprotocol can be defined by having *both* one or more protocols as | |
| immediate base classes and also having ``typing.Protocol`` as an immediate | |
| base class:: | |
| from typing import Sized, Protocol | |
| class SizedAndClosable(Sized, Protocol): | |
| def close(self) -> None: | |
| ... | |
| Now the protocol ``SizedAndClosable`` is a protocol with two methods, | |
| ``__len__`` and ``close``. If one omits ``Protocol`` in the base class list, | |
| this would be a regular (non-protocol) class that must implement ``Sized``. | |
| Alternatively, one can implement ``SizedAndClosable`` protocol by merging | |
| the ``SupportsClose`` protocol from the example in the `definition`_ section | |
| with ``typing.Sized``:: | |
| from typing import Sized | |
| class SupportsClose(Protocol): | |
| def close(self) -> None: | |
| ... | |
| class SizedAndClosable(Sized, SupportsClose, Protocol): | |
| pass | |
| The two definitions of ``SizedAndClosable`` are equivalent. | |
| Subclass relationships between protocols are not meaningful when | |
| considering subtyping, since structural compatibility is | |
| the criterion, not the MRO. | |
| If ``Protocol`` is included in the base class list, all the other base classes | |
| must be protocols. A protocol can't extend a regular class, see `rejected`_ | |
| ideas for rationale. Note that rules around explicit subclassing are different | |
| from regular ABCs, where abstractness is simply defined by having at least one | |
| abstract method being unimplemented. Protocol classes must be marked | |
| *explicitly*. | |
| Generic protocols | |
| ----------------- | |
| Generic protocols are important. For example, ``SupportsAbs``, ``Iterable`` | |
| and ``Iterator`` are generic protocols. They are defined similar to normal | |
| non-protocol generic types:: | |
| class Iterable(Protocol[T]): | |
| @abstractmethod | |
| def __iter__(self) -> Iterator[T]: | |
| ... | |
| ``Protocol[T, S, ...]`` is allowed as a shorthand for | |
| ``Protocol, Generic[T, S, ...]``. | |
| User-defined generic protocols support explicitly declared variance. | |
| Type checkers will warn if the inferred variance is different from | |
| the declared variance. Examples:: | |
| T = TypeVar('T') | |
| T_co = TypeVar('T_co', covariant=True) | |
| T_contra = TypeVar('T_contra', contravariant=True) | |
| class Box(Protocol[T_co]): | |
| def content(self) -> T_co: | |
| ... | |
| box: Box[float] | |
| second_box: Box[int] | |
| box = second_box # This is OK due to the covariance of 'Box'. | |
| class Sender(Protocol[T_contra]): | |
| def send(self, data: T_contra) -> int: | |
| ... | |
| sender: Sender[float] | |
| new_sender: Sender[int] | |
| new_sender = sender # OK, 'Sender' is contravariant. | |
| class Proto(Protocol[T]): | |
| attr: T # this class is invariant, since it has a mutable attribute | |
| var: Proto[float] | |
| another_var: Proto[int] | |
| var = another_var # Error! 'Proto[float]' is incompatible with 'Proto[int]'. | |
| Note that unlike nominal classes, de facto covariant protocols cannot be | |
| declared as invariant, since this can break transitivity of subtyping | |
| (see `rejected`_ ideas for details). For example:: | |
| T = TypeVar('T') | |
| class AnotherBox(Protocol[T]): # Error, this protocol is covariant in T, | |
| def content(self) -> T: # not invariant. | |
| ... | |
| Recursive protocols | |
| ------------------- | |
| Recursive protocols are also supported. Forward references to the protocol | |
| class names can be given as strings as specified by PEP 484. Recursive | |
| protocols are useful for representing self-referential data structures | |
| like trees in an abstract fashion:: | |
| class Traversable(Protocol): | |
| def leaves(self) -> Iterable['Traversable']: | |
| ... | |
| Note that for recursive protocols, a class is considered a subtype of | |
| the protocol in situations where the decision depends on itself. | |
| Continuing the previous example:: | |
| class SimpleTree: | |
| def leaves(self) -> List['SimpleTree']: | |
| ... | |
| root: Traversable = SimpleTree() # OK | |
| class Tree(Generic[T]): | |
| def leaves(self) -> List['Tree[T]']: | |
| ... | |
| def walk(graph: Traversable) -> None: | |
| ... | |
| tree: Tree[float] = Tree() | |
| walk(tree) # OK, 'Tree[float]' is a subtype of 'Traversable' | |
| Self-types in protocols | |
| ----------------------- | |
| The self-types in protocols follow the corresponding specification | |
| [self-types]_ of PEP 484. For example:: | |
| C = TypeVar('C', bound='Copyable') | |
| class Copyable(Protocol): | |
| def copy(self: C) -> C: | |
| class One: | |
| def copy(self) -> 'One': | |
| ... | |
| T = TypeVar('T', bound='Other') | |
| class Other: | |
| def copy(self: T) -> T: | |
| ... | |
| c: Copyable | |
| c = One() # OK | |
| c = Other() # Also OK | |
| Callback protocols | |
| ------------------ | |
| Protocols can be used to define flexible callback types that are hard | |
| (or even impossible) to express using the ``Callable[...]`` syntax | |
| specified by PEP 484, such as variadic, overloaded, and complex generic | |
| callbacks. They can be defined as protocols with a ``__call__`` member:: | |
| from typing import Optional, List, Protocol | |
| class Combiner(Protocol): | |
| def __call__(self, *vals: bytes, | |
| maxlen: Optional[int] = None) -> List[bytes]: ... | |
| def good_cb(*vals: bytes, maxlen: Optional[int] = None) -> List[bytes]: | |
| ... | |
| def bad_cb(*vals: bytes, maxitems: Optional[int]) -> List[bytes]: | |
| ... | |
| comb: Combiner = good_cb # OK | |
| comb = bad_cb # Error! Argument 2 has incompatible type because of | |
| # different name and kind in the callback | |
| Callback protocols and ``Callable[...]`` types can be used interchangeably. | |
| Using Protocols | |
| =============== | |
| Subtyping relationships with other types | |
| ---------------------------------------- | |
| Protocols cannot be instantiated, so there are no values whose | |
| runtime type is a protocol. For variables and parameters with protocol types, | |
| subtyping relationships are subject to the following rules: | |
| * A protocol is never a subtype of a concrete type. | |
| * A concrete type ``X`` is a subtype of protocol ``P`` | |
| if and only if ``X`` implements all protocol members of ``P`` with | |
| compatible types. In other words, subtyping with respect to a protocol is | |
| always structural. | |
| * A protocol ``P1`` is a subtype of another protocol ``P2`` if ``P1`` defines | |
| all protocol members of ``P2`` with compatible types. | |
| Generic protocol types follow the same rules of variance as non-protocol | |
| types. Protocol types can be used in all contexts where any other types | |
| can be used, such as in ``Union``, ``ClassVar``, type variables bounds, etc. | |
| Generic protocols follow the rules for generic abstract classes, except for | |
| using structural compatibility instead of compatibility defined by | |
| inheritance relationships. | |
| Static type checkers will recognize protocol implementations, even if the | |
| corresponding protocols are *not imported*:: | |
| # file lib.py | |
| from typing import Sized | |
| T = TypeVar('T', contravariant=True) | |
| class ListLike(Sized, Protocol[T]): | |
| def append(self, x: T) -> None: | |
| pass | |
| def populate(lst: ListLike[int]) -> None: | |
| ... | |
| # file main.py | |
| from lib import populate # Note that ListLike is NOT imported | |
| class MockStack: | |
| def __len__(self) -> int: | |
| return 42 | |
| def append(self, x: int) -> None: | |
| print(x) | |
| populate([1, 2, 3]) # Passes type check | |
| populate(MockStack()) # Also OK | |
| Unions and intersections of protocols | |
| ------------------------------------- | |
| ``Union`` of protocol classes behaves the same way as for non-protocol | |
| classes. For example:: | |
| from typing import Union, Optional, Protocol | |
| class Exitable(Protocol): | |
| def exit(self) -> int: | |
| ... | |
| class Quittable(Protocol): | |
| def quit(self) -> Optional[int]: | |
| ... | |
| def finish(task: Union[Exitable, Quittable]) -> int: | |
| ... | |
| class DefaultJob: | |
| ... | |
| def quit(self) -> int: | |
| return 0 | |
| finish(DefaultJob()) # OK | |
| One can use multiple inheritance to define an intersection of protocols. | |
| Example:: | |
| from typing import Iterable, Hashable | |
| class HashableFloats(Iterable[float], Hashable, Protocol): | |
| pass | |
| def cached_func(args: HashableFloats) -> float: | |
| ... | |
| cached_func((1, 2, 3)) # OK, tuple is both hashable and iterable | |
| If this will prove to be a widely used scenario, then a special | |
| intersection type construct could be added in future as specified by PEP 483, | |
| see `rejected`_ ideas for more details. | |
| ``Type[]`` and class objects vs protocols | |
| ----------------------------------------- | |
| Variables and parameters annotated with ``Type[Proto]`` accept only concrete | |
| (non-protocol) subtypes of ``Proto``. The main reason for this is to allow | |
| instantiation of parameters with such type. For example:: | |
| class Proto(Protocol): | |
| @abstractmethod | |
| def meth(self) -> int: | |
| ... | |
| class Concrete: | |
| def meth(self) -> int: | |
| return 42 | |
| def fun(cls: Type[Proto]) -> int: | |
| return cls().meth() # OK | |
| fun(Proto) # Error | |
| fun(Concrete) # OK | |
| The same rule applies to variables:: | |
| var: Type[Proto] | |
| var = Proto # Error | |
| var = Concrete # OK | |
| var().meth() # OK | |
| Assigning an ABC or a protocol class to a variable is allowed if it is | |
| not explicitly typed, and such assignment creates a type alias. | |
| For normal (non-abstract) classes, the behavior of ``Type[]`` is | |
| not changed. | |
| A class object is considered an implementation of a protocol if accessing | |
| all members on it results in types compatible with the protocol members. | |
| For example:: | |
| from typing import Any, Protocol | |
| class ProtoA(Protocol): | |
| def meth(self, x: int) -> int: ... | |
| class ProtoB(Protocol): | |
| def meth(self, obj: Any, x: int) -> int: ... | |
| class C: | |
| def meth(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 type | |
| checkers might refuse protocol classes inside ``NewType()`` to avoid an | |
| illusion that a distinct type is provided:: | |
| from typing import NewType, Protocol, Iterator | |
| class Id(Protocol): | |
| code: int | |
| secrets: Iterator[bytes] | |
| UserId = NewType('UserId', Id) # Error, can't provide distinct type | |
| In contrast, type aliases are fully supported, including generic type | |
| aliases:: | |
| from typing import TypeVar, Reversible, Iterable, Sized | |
| T = TypeVar('T') | |
| class SizedIterable(Iterable[T], Sized, Protocol): | |
| pass | |
| CompatReversible = Union[Reversible[T], SizedIterable[T]] | |
| Modules as implementations of protocols | |
| --------------------------------------- | |
| A module object is accepted where a protocol is expected if the public | |
| interface of the given module is compatible with the expected protocol. | |
| For example:: | |
| # file default_config.py | |
| timeout = 100 | |
| one_flag = True | |
| other_flag = False | |
| # file main.py | |
| import default_config | |
| from typing import Protocol | |
| class Options(Protocol): | |
| timeout: int | |
| one_flag: bool | |
| other_flag: bool | |
| def setup(options: Options) -> None: | |
| ... | |
| setup(default_config) # OK | |
| To determine compatibility of module level functions, the ``self`` argument | |
| of the corresponding protocol methods is dropped. For example:: | |
| # callbacks.py | |
| def on_error(x: int) -> None: | |
| ... | |
| def on_success() -> None: | |
| ... | |
| # main.py | |
| import callbacks | |
| from typing import Protocol | |
| class Reporter(Protocol): | |
| def on_error(self, x: int) -> None: | |
| ... | |
| def on_success(self) -> None: | |
| ... | |
| rp: Reporter = callbacks # Passes type check | |
| .. _discussion: | |
| ``@runtime_checkable`` decorator and narrowing types by ``isinstance()`` | |
| ------------------------------------------------------------------------ | |
| The default semantics is that ``isinstance()`` and ``issubclass()`` fail | |
| for protocol types. This is in the spirit of duck typing -- protocols | |
| basically would be used to model duck typing statically, not explicitly | |
| at runtime. | |
| However, it should be possible for protocol types to implement custom | |
| instance and class checks when this makes sense, similar to how ``Iterable`` | |
| and other ABCs in ``collections.abc`` and ``typing`` already do it, | |
| but this is limited to non-generic and unsubscripted generic protocols | |
| (``Iterable`` is statically equivalent to ``Iterable[Any]``). | |
| The ``typing`` module will define a special ``@runtime_checkable`` class decorator | |
| that provides the same semantics for class and instance checks as for | |
| ``collections.abc`` classes, essentially making them "runtime protocols":: | |
| from typing import runtime_checkable, Protocol | |
| @runtime_checkable | |
| class SupportsClose(Protocol): | |
| def close(self): | |
| ... | |
| assert isinstance(open('some/file'), SupportsClose) | |
| Note that instance checks are not 100% reliable statically, this is why | |
| this behavior is opt-in, see section on `rejected`_ ideas for examples. | |
| The most type checkers can do is to treat ``isinstance(obj, Iterator)`` | |
| roughly as a simpler way to write | |
| ``hasattr(x, '__iter__') and hasattr(x, '__next__')``. To minimize | |
| the risks for this feature, the following rules are applied. | |
| **Definitions**: | |
| * *Data, and non-data protocols*: A protocol is called non-data protocol | |
| if it only contains methods as members (for example ``Sized``, | |
| ``Iterator``, etc). A protocol that contains at least one non-method member | |
| (like ``x: int``) is called a data protocol. | |
| * *Unsafe overlap*: A type ``X`` is called unsafely overlapping with | |
| a protocol ``P``, if ``X`` is not a subtype of ``P``, but it is a subtype | |
| of the type erased version of ``P`` where all members have type ``Any``. | |
| In addition, if at least one element of a union unsafely overlaps with | |
| a protocol ``P``, then the whole union is unsafely overlapping with ``P``. | |
| **Specification**: | |
| * A protocol can be used as a second argument in ``isinstance()`` and | |
| ``issubclass()`` only if it is explicitly opt-in by ``@runtime_checkable`` | |
| decorator. This requirement exists because protocol checks are not type safe | |
| in case of dynamically set attributes, and because type checkers can only prove | |
| that an ``isinstance()`` check is safe only for a given class, not for all its | |
| subclasses. | |
| * ``isinstance()`` can be used with both data and non-data protocols, while | |
| ``issubclass()`` can be used only with non-data protocols. This restriction | |
| exists because some data attributes can be set on an instance in constructor | |
| and this information is not always available on the class object. | |
| * Type checkers should reject an ``isinstance()`` or ``issubclass()`` call, if | |
| there is an unsafe overlap between the type of the first argument and | |
| the protocol. | |
| * Type checkers should be able to select a correct element from a union after | |
| a safe ``isinstance()`` or ``issubclass()`` call. For narrowing from non-union | |
| types, type checkers can use their best judgement (this is intentionally | |
| unspecified, since a precise specification would require intersection types). | |
| Using Protocols in Python 2.7 - 3.5 | |
| =================================== | |
| Variable annotation syntax was added in Python 3.6, so that the syntax | |
| for defining protocol variables proposed in `specification`_ section can't | |
| be used if support for earlier versions is needed. To define these | |
| in a manner compatible with older versions of Python one can use properties. | |
| Properties can be settable and/or abstract if needed:: | |
| class Foo(Protocol): | |
| @property | |
| def c(self) -> int: | |
| return 42 # Default value can be provided for property... | |
| @abstractproperty | |
| def d(self) -> int: # ... or it can be abstract | |
| return 0 | |
| Also function type comments can be used as per PEP 484 (for example | |
| to provide compatibility with Python 2). The ``typing`` module changes | |
| proposed in this PEP will also be backported to earlier versions via the | |
| backport currently available on PyPI. | |
| Runtime Implementation of Protocol Classes | |
| ========================================== | |
| Implementation details | |
| ---------------------- | |
| The runtime implementation could be done in pure Python without any | |
| effects on the core interpreter and standard library except in the | |
| ``typing`` module, and a minor update to ``collections.abc``: | |
| * Define class ``typing.Protocol`` similar to ``typing.Generic``. | |
| * Implement functionality to detect whether a class is | |
| a protocol or not. Add a class attribute ``_is_protocol = True`` | |
| if that is the case. Verify that a protocol class only has protocol | |
| base classes in the MRO (except for object). | |
| * Implement ``@runtime_checkable`` that allows ``__subclasshook__()`` | |
| performing structural instance and subclass checks as in ``collections.abc`` | |
| classes. | |
| * All structural subtyping checks will be performed by static type checkers, | |
| such as ``mypy`` [mypy]_. No additional support for protocol validation will | |
| be provided at runtime. | |
| Changes in the typing module | |
| ---------------------------- | |
| The following classes in ``typing`` module will be protocols: | |
| * ``Callable`` | |
| * ``Awaitable`` | |
| * ``Iterable``, ``Iterator`` | |
| * ``AsyncIterable``, ``AsyncIterator`` | |
| * ``Hashable`` | |
| * ``Sized`` | |
| * ``Container`` | |
| * ``Collection`` | |
| * ``Reversible`` | |
| * ``ContextManager``, ``AsyncContextManager`` | |
| * ``SupportsAbs`` (and other ``Supports*`` classes) | |
| Most of these classes are small and conceptually simple. It is easy to see | |
| what are the methods these protocols implement, and immediately recognize | |
| the corresponding runtime protocol counterpart. | |
| Practically, few changes will be needed in ``typing`` since some of these | |
| classes already behave the necessary way at runtime. Most of these will need | |
| to be updated only in the corresponding ``typeshed`` stubs [typeshed]_. | |
| All other concrete generic classes such as ``List``, ``Set``, ``IO``, | |
| ``Deque``, etc are sufficiently complex that it makes sense to keep | |
| them non-protocols (i.e. require code to be explicit about them). Also, it is | |
| too easy to leave some methods unimplemented by accident, and explicitly | |
| marking the subclass relationship allows type checkers to pinpoint the missing | |
| implementations. | |
| Introspection | |
| ------------- | |
| The existing class introspection machinery (``dir``, ``__annotations__`` etc) | |
| can be used with protocols. In addition, all introspection tools implemented | |
| in the ``typing`` module will support protocols. Since all attributes need | |
| to be defined in the class body based on this proposal, protocol classes will | |
| have even better perspective for introspection than regular classes where | |
| attributes can be defined implicitly -- protocol attributes can't be | |
| initialized in ways that are not visible to introspection | |
| (using ``setattr()``, assignment via ``self``, etc.). Still, some things like | |
| types of attributes will not be visible at runtime in Python 3.5 and earlier, | |
| but this looks like a reasonable limitation. | |
| There will be only limited support of ``isinstance()`` and ``issubclass()`` | |
| as discussed above (these will *always* fail with ``TypeError`` for | |
| subscripted generic protocols, since a reliable answer could not be given | |
| at runtime in this case). But together with other introspection tools this | |
| give a reasonable perspective for runtime type checking tools. | |
| .. _rejected: | |
| Rejected/Postponed Ideas | |
| ======================== | |
| The ideas in this section were previously discussed in [several]_ | |
| [discussions]_ [elsewhere]_. | |
| Make every class a protocol by default | |
| -------------------------------------- | |
| Some languages such as Go make structural subtyping the only or the primary | |
| form of subtyping. We could achieve a similar result by making all classes | |
| protocols by default (or even always). However we believe that it is better | |
| to require classes to be explicitly marked as protocols, for the following | |
| reasons: | |
| * Protocols don't have some properties of regular classes. In particular, | |
| ``isinstance()``, as defined for normal classes, is based on the nominal | |
| hierarchy. In order to make everything a protocol by default, and have | |
| ``isinstance()`` work would require changing its semantics, | |
| which won't happen. | |
| * Protocol classes should generally not have many method implementations, | |
| as they describe an interface, not an implementation. | |
| Most classes have many method implementations, making them bad protocol | |
| classes. | |
| * Experience suggests that many classes are not practical as protocols anyway, | |
| mainly because their interfaces are too large, complex or | |
| implementation-oriented (for example, they may include de facto | |
| private attributes and methods without a ``__`` prefix). | |
| * Most actually useful protocols in existing Python code seem to be implicit. | |
| The ABCs in ``typing`` and ``collections.abc`` are rather an exception, but | |
| even they are recent additions to Python and most programmers | |
| do not use them yet. | |
| * Many built-in functions only accept concrete instances of ``int`` | |
| (and subclass instances), and similarly for other built-in classes. Making | |
| ``int`` a structural type wouldn't be safe without major changes to the | |
| Python runtime, which won't happen. | |
| Protocols subclassing normal classes | |
| ------------------------------------ | |
| The main rationale to prohibit this is to preserve transitivity of subtyping, | |
| consider this example:: | |
| from typing import Protocol | |
| class Base: | |
| attr: str | |
| class Proto(Base, Protocol): | |
| def meth(self) -> int: | |
| ... | |
| class C: | |
| attr: str | |
| def meth(self) -> int: | |
| return 0 | |
| Now, ``C`` is a subtype of ``Proto``, and ``Proto`` is a subtype of ``Base``. | |
| But ``C`` cannot be a subtype of ``Base`` (since the latter is not | |
| a protocol). This situation would be really weird. In addition, there is | |
| an ambiguity about whether attributes of ``Base`` should become protocol | |
| members of ``Proto``. | |
| Support optional protocol members | |
| --------------------------------- | |
| We can come up with examples where it would be handy to be able to say | |
| that a method or data attribute does not need to be present in a class | |
| implementing a protocol, but if it is present, it must conform to a specific | |
| signature or type. One could use a ``hasattr()`` check to determine whether | |
| they can use the attribute on a particular instance. | |
| Languages such as TypeScript have similar features and | |
| apparently they are pretty commonly used. The current realistic potential | |
| use cases for protocols in Python don't require these. In the interest | |
| of simplicity, we propose to not support optional methods or attributes. | |
| We can always revisit this later if there is an actual need. | |
| Allow only protocol methods and force use of getters and setters | |
| ---------------------------------------------------------------- | |
| One could argue that protocols typically only define methods, but not | |
| variables. However, using getters and setters in cases where only a | |
| simple variable is needed would be quite unpythonic. Moreover, the widespread | |
| use of properties (that often act as type validators) in large code bases | |
| is partially due to previous absence of static type checkers for Python, | |
| the problem that PEP 484 and this PEP are aiming to solve. For example:: | |
| # without static types | |
| class MyClass: | |
| @property | |
| def my_attr(self): | |
| return self._my_attr | |
| @my_attr.setter | |
| def my_attr(self, value): | |
| if not isinstance(value, int): | |
| raise ValidationError("An integer expected for my_attr") | |
| self._my_attr = value | |
| # with static types | |
| class MyClass: | |
| my_attr: int | |
| Support non-protocol members | |
| ---------------------------- | |
| There was an idea to make some methods "non-protocol" (i.e. not necessary | |
| to implement, and inherited in explicit subclassing), but it was rejected, | |
| since this complicates things. For example, consider this situation:: | |
| class Proto(Protocol): | |
| @abstractmethod | |
| def first(self) -> int: | |
| raise NotImplementedError | |
| def second(self) -> int: | |
| return self.first() + 1 | |
| def fun(arg: Proto) -> None: | |
| arg.second() | |
| The question is should this be an error? We think most people would expect | |
| this to be valid. Therefore, to be on the safe side, we need to require both | |
| methods to be implemented in implicit subclasses. In addition, if one looks | |
| at definitions in ``collections.abc``, there are very few methods that could | |
| be considered "non-protocol". Therefore, it was decided to not introduce | |
| "non-protocol" methods. | |
| There is only one downside to this: it will require some boilerplate for | |
| implicit subtypes of "large" protocols. But, this doesn't apply to "built-in" | |
| protocols that are all "small" (i.e. have only few abstract methods). | |
| Also, such style is discouraged for user-defined protocols. It is recommended | |
| to create compact protocols and combine them. | |
| Make protocols interoperable with other approaches | |
| -------------------------------------------------- | |
| The protocols as described here are basically a minimal extension to | |
| the existing concept of ABCs. We argue that this is the way they should | |
| be understood, instead of as something that *replaces* Zope interfaces, | |
| for example. Attempting such interoperabilities will significantly | |
| complicate both the concept and the implementation. | |
| On the other hand, Zope interfaces are conceptually a superset of protocols | |
| defined here, but using an incompatible syntax to define them, | |
| because before PEP 526 there was no straightforward way to annotate attributes. | |
| In the 3.6+ world, ``zope.interface`` might potentially adopt the ``Protocol`` | |
| syntax. In this case, type checkers could be taught to recognize interfaces | |
| as protocols and make simple structural checks with respect to them. | |
| Use assignments to check explicitly that a class implements a protocol | |
| ---------------------------------------------------------------------- | |
| In the Go language the explicit checks for implementation are performed | |
| via dummy assignments [golang]_. Such a way is also possible with the | |
| current proposal. Example:: | |
| class A: | |
| def __len__(self) -> float: | |
| return ... | |
| _: Sized = A() # Error: A.__len__ doesn't conform to 'Sized' | |
| # (Incompatible return type 'float') | |
| This approach moves the check away from | |
| the class definition and it almost requires a comment as otherwise | |
| the code probably would not make any sense to an average reader | |
| -- it looks like dead code. Besides, in the simplest form it requires one | |
| to construct an instance of ``A``, which could be problematic if this requires | |
| accessing or allocating some resources such as files or sockets. | |
| We could work around the latter by using a cast, for example, but then | |
| the code would be ugly. Therefore, we discourage the use of this pattern. | |
| Support ``isinstance()`` checks by default | |
| ------------------------------------------ | |
| The problem with this is instance checks could be unreliable, except for | |
| situations where there is a common signature convention such as ``Iterable``. | |
| For example:: | |
| class P(Protocol): | |
| def common_method_name(self, x: int) -> int: ... | |
| class X: | |
| <a bunch of methods> | |
| def common_method_name(self) -> None: ... # Note different signature | |
| def do_stuff(o: Union[P, X]) -> int: | |
| if isinstance(o, P): | |
| return o.common_method_name(1) # Results in TypeError not caught | |
| # statically if o is an X instance. | |
| Another potentially problematic case is assignment of attributes | |
| *after* instantiation:: | |
| class P(Protocol): | |
| x: int | |
| class C: | |
| def initialize(self) -> None: | |
| self.x = 0 | |
| c = C() | |
| isinstance(c, P) # False | |
| c.initialize() | |
| isinstance(c, P) # True | |
| def f(x: Union[P, int]) -> None: | |
| if isinstance(x, P): | |
| # Static type of x is P here. | |
| ... | |
| else: | |
| # Static type of x is int, but can be other type at runtime... | |
| print(x + 1) | |
| f(C()) # ...causing a TypeError. | |
| We argue that requiring an explicit class decorator would be better, since | |
| one can then attach warnings about problems like this in the documentation. | |
| The user would be able to evaluate whether the benefits outweigh | |
| the potential for confusion for each protocol and explicitly opt in -- but | |
| the default behavior would be safer. Finally, it will be easy to make this | |
| behavior default if necessary, while it might be problematic to make it opt-in | |
| after being default. | |
| Provide a special intersection type construct | |
| --------------------------------------------- | |
| There was an idea to allow ``Proto = All[Proto1, Proto2, ...]`` as a shorthand | |
| for:: | |
| class Proto(Proto1, Proto2, ..., Protocol): | |
| pass | |
| However, it is not yet clear how popular/useful it will be and implementing | |
| this in type checkers for non-protocol classes could be difficult. Finally, it | |
| will be very easy to add this later if needed. | |
| Prohibit explicit subclassing of protocols by non-protocols | |
| ----------------------------------------------------------- | |
| This was rejected for the following reasons: | |
| * Backward compatibility: People are already using ABCs, including generic | |
| ABCs from ``typing`` module. If we prohibit explicit subclassing of these | |
| ABCs, then quite a lot of code will break. | |
| * Convenience: There are existing protocol-like ABCs (that may be turned | |
| into protocols) that have many useful "mix-in" (non-abstract) methods. | |
| For example, in the case of ``Sequence`` one only needs to implement | |
| ``__getitem__`` and ``__len__`` in an explicit subclass, and one gets | |
| ``__iter__``, ``__contains__``, ``__reversed__``, ``index``, and ``count`` | |
| for free. | |
| * Explicit subclassing makes it explicit that a class implements a particular | |
| protocol, making subtyping relationships easier to see. | |
| * Type checkers can warn about missing protocol members or members with | |
| incompatible types more easily, without having to use hacks like dummy | |
| assignments discussed above in this section. | |
| * Explicit subclassing makes it possible to force a class to be considered | |
| a subtype of a protocol (by using ``# type: ignore`` together with an | |
| explicit base class) when it is not strictly compatible, such as when | |
| it has an unsafe override. | |
| Covariant subtyping of mutable attributes | |
| ----------------------------------------- | |
| Rejected because covariant subtyping of mutable attributes is not safe. | |
| Consider this example:: | |
| class P(Protocol): | |
| x: float | |
| def f(arg: P) -> None: | |
| arg.x = 0.42 | |
| class C: | |
| x: int | |
| c = C() | |
| f(c) # Would typecheck if covariant subtyping | |
| # of mutable attributes were allowed. | |
| c.x >> 1 # But this fails at runtime | |
| It was initially proposed to allow this for practical reasons, but it was | |
| subsequently rejected, since this may mask some hard to spot bugs. | |
| Overriding inferred variance of protocol classes | |
| ------------------------------------------------ | |
| It was proposed to allow declaring protocols as invariant if they are actually | |
| covariant or contravariant (as it is possible for nominal classes, see PEP 484). | |
| However, it was decided not to do this because of several downsides: | |
| * Declared protocol invariance breaks transitivity of sub-typing. Consider | |
| this situation:: | |
| T = TypeVar('T') | |
| class P(Protocol[T]): # Protocol is declared as invariant. | |
| def meth(self) -> T: | |
| ... | |
| class C: | |
| def meth(self) -> float: | |
| ... | |
| class D(C): | |
| def meth(self) -> int: | |
| ... | |
| Now we have that ``D`` is a subtype of ``C``, and ``C`` is a subtype of | |
| ``P[float]``. But ``D`` is *not* a subtype of ``P[float]`` since ``D`` | |
| implements ``P[int]``, and ``P`` is invariant. There is a possibility | |
| to "cure" this by looking for protocol implementations in MROs but this | |
| will be too complex in a general case, and this "cure" requires abandoning | |
| simple idea of purely structural subtyping for protocols. | |
| * Subtyping checks will always require type inference for protocols. In the | |
| above example a user may complain: "Why did you infer ``P[int]`` for | |
| my ``D``? It implements ``P[float]``!". Normally, inference can be overruled | |
| by an explicit annotation, but here this will require explicit subclassing, | |
| defeating the purpose of using protocols. | |
| * Allowing overriding variance will make impossible more detailed error | |
| messages in type checkers citing particular conflicts in member | |
| type signatures. | |
| * Finally, explicit is better than implicit in this case. Requiring user to | |
| declare correct variance will simplify understanding the code and will avoid | |
| unexpected errors at the point of use. | |
| Support adapters and adaptation | |
| ------------------------------- | |
| Adaptation was proposed by PEP 246 (rejected) and is supported by | |
| ``zope.interface``, see https://docs.zope.org/zope.interface/adapter.html. | |
| Adapters is quite an advanced concept, and PEP 484 supports unions and | |
| generic aliases that can be used instead of adapters. This can be illustrated | |
| with an example of ``Iterable`` protocol, there is another way of supporting | |
| iteration by providing ``__getitem__`` and ``__len__``. If a function | |
| supports both this way and the now standard ``__iter__`` method, then it could | |
| be annotated by a union type:: | |
| class OldIterable(Sized, Protocol[T]): | |
| def __getitem__(self, item: int) -> T: ... | |
| CompatIterable = Union[Iterable[T], OldIterable[T]] | |
| class A: | |
| def __iter__(self) -> Iterator[str]: ... | |
| class B: | |
| def __len__(self) -> int: ... | |
| def __getitem__(self, item: int) -> str: ... | |
| def iterate(it: CompatIterable[str]) -> None: | |
| ... | |
| iterate(A()) # OK | |
| iterate(B()) # OK | |
| Since there is a reasonable alternative for such cases with existing tooling, | |
| it is therefore proposed not to include adaptation in this PEP. | |
| Call structural base types "interfaces" | |
| --------------------------------------- | |
| "Protocol" is a term already widely used in Python to describe duck typing | |
| contracts such as the iterator protocol (providing ``__iter__`` | |
| and ``__next__``), and the descriptor protocol (providing ``__get__``, | |
| ``__set__``, and ``__delete__``). In addition to this and other reasons given | |
| in `specification`_, protocols are different from Java interfaces in several | |
| aspects: protocols don't require explicit declaration of implementation | |
| (they are mainly oriented on duck-typing), protocols can have | |
| default implementations of members and store state. | |
| Make protocols special objects at runtime rather than normal ABCs | |
| ----------------------------------------------------------------- | |
| Making protocols non-ABCs will make the backwards compatibility problematic | |
| if possible at all. For example, ``collections.abc.Iterable`` is already | |
| an ABC, and lots of existing code use patterns like | |
| ``isinstance(obj, collections.abc.Iterable)`` and similar checks with other | |
| ABCs (also in a structural manner, i.e., via ``__subclasshook__``). | |
| Disabling this behavior will cause breakages. If we keep this behavior | |
| for ABCs in ``collections.abc`` but will not provide a similar runtime | |
| behavior for protocols in ``typing``, then a smooth transition to protocols | |
| will be not possible. In addition, having two parallel hierarchies may cause | |
| confusions. | |
| Backwards Compatibility | |
| ======================= | |
| This PEP is fully backwards compatible. | |
| Implementation | |
| ============== | |
| The ``mypy`` type checker fully supports protocols (modulo a few | |
| known bugs). This includes treating all the builtin protocols, such as | |
| ``Iterable`` structurally. The runtime implementation of protocols is | |
| available in ``typing_extensions`` module on PyPI. | |
| References | |
| ========== | |
| .. [typing] | |
| https://docs.python.org/3/library/typing.html | |
| .. [wiki-structural] | |
| https://en.wikipedia.org/wiki/Structural_type_system | |
| .. [zope-interfaces] | |
| https://zopeinterface.readthedocs.io/en/latest/ | |
| .. [abstract-classes] | |
| https://docs.python.org/3/library/abc.html | |
| .. [collections-abc] | |
| https://docs.python.org/3/library/collections.abc.html | |
| .. [typescript] | |
| https://www.typescriptlang.org/docs/handbook/interfaces.html | |
| .. [golang] | |
| https://golang.org/doc/effective_go.html#interfaces_and_types | |
| .. [data-model] | |
| https://docs.python.org/3/reference/datamodel.html#special-method-names | |
| .. [typeshed] | |
| https://github.com/python/typeshed/ | |
| .. [mypy] | |
| http://github.com/python/mypy/ | |
| .. [several] | |
| https://mail.python.org/pipermail/python-ideas/2015-September/thread.html#35859 | |
| .. [discussions] | |
| https://github.com/python/typing/issues/11 | |
| .. [elsewhere] | |
| https://github.com/python/peps/pull/224 | |
| .. [self-types] | |
| https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods | |
| Copyright | |
| ========= | |
| This document has been placed in the public domain. | |
| .. | |
| Local Variables: | |
| mode: indented-text | |
| indent-tabs-mode: nil | |
| sentence-end-double-space: t | |
| fill-column: 70 | |
| coding: utf-8 | |
| End: |