Skip to content

Instantly share code, notes, and snippets.

@zhukovgreen
Last active February 12, 2024 14:42
Show Gist options
  • Save zhukovgreen/8cedd8e6f0d4a776aad37aab59db426e to your computer and use it in GitHub Desktop.
Save zhukovgreen/8cedd8e6f0d4a776aad37aab59db426e to your computer and use it in GitHub Desktop.
python protocols notes

Typing Callable is quite limited and truncated in a sense of type hinting support. Protocols can be used to annotate more complex types of callables.

from typing import Callable, Protocol


class ComplexCallableLike(Protocol):
    def __call__(self, a: str, *, kwarg1: int) -> float: ...


def foo(self, a: str, *, kwarg1: int): ...


foo_like_callable: Callable[[str, int], float]
foo_like_protocol: ComplexCallableLike

foo_like_callable()
foo_like_protocol()

Type-cheking:

error: Too few arguments  [call-arg]
error: Missing positional argument "a" in call to "__call__" of "ComplexCallableLike"  [call-arg]
error: Missing named argument "kwarg1" for "__call__" of "ComplexCallableLike"  [call-arg]

Also the type hinting in the IDE:

  • Callable:
image
  • Protocol
image

When protocol is implemented by inheriting the class from it, this called explicit implementation of protocols. See the following snippet:

from typing import Protocol

import attrs


dataclass = attrs.define(auto_attribs=True, frozen=True)


@dataclass
class ExplicitBaseA(Protocol):
    attr1: str
    attr2: str


@dataclass
class ImplicitBaseA(Protocol):
    attr3: str
    attr4: str


@dataclass
class ImplementsImplicitBaseA: ...


ImplementsImplicitBaseA()


@dataclass
class ImplementsExplicitBaseA(ExplicitBaseA): ...


ImplementsExplicitBaseA()

When type-checking with mypy:

$ mypy ./implicit_vs_explicit.py

src/protocols_demo/implicit_vs_explicit.py:32: error: 
Cannot instantiate abstract class "ImplementsExplicitBaseA" 
with abstract attributes "attr1" and "attr2"  [abstract]

Notice, that ImplementsImplicitBaseA is type-checked well, since mypy do not know yet, that this is suppose to be ImplicitBaseA.

As well as IDE - it doesn't know this fact and do not play its role (speeding you up) well compared to ImplementsExplicitBaseA case.

image

bind defaults

Other advantage of explicit protocols is that you can bind a default implementations to it.

from typing import Protocol

import attrs


dataclass = attrs.define(auto_attribs=True, frozen=True)


@dataclass
class ExplicitBaseA(Protocol):
    attr1: str
    attr2: str

    attr_with_default: int = 15

    def method(self) -> str: ...

    def method_with_default(self) -> str:
        return "default"


@dataclass
class ImplementsExplicitBaseA(ExplicitBaseA): ...


ImplementsExplicitBaseA()

When type checking:

error: Cannot instantiate abstract class "ImplementsExplicitBaseA" with abstract attributes "attr1", "attr2" and "method"  [abstract]

Notice that attr_with_default and method_with_default is not specified in the error.

Nested protocols allows extending protocols by other protocols, including basic inheritance or mixin approaches. It is just important to add Protocol at the end when inheriting. For example:

from typing import Protocol

import attrs


dataclass = attrs.define(auto_attribs=True, frozen=True)


@dataclass
class Human(Protocol):
    attr1: str
    attr2: str


@dataclass
class GoodHuman(Human, Protocol):
    attr3: str


@dataclass
class BadHuman(Human, Protocol):
    attr4: str


class ImplementGoodHuman(GoodHuman): ...


class ImplementBadHuman(BadHuman): ...


ImplementGoodHuman()
ImplementBadHuman()


class CanJump(Protocol):
    def jump(self) -> None: ...


class CanSwim(Protocol):
    def smim(self) -> None: ...


class JumpoSwim(CanJump, CanSwim, Protocol): ...


class RealJumpoSmim(JumpoSwim): ...


RealJumpoSmim()

When type-checking:

error: Cannot instantiate abstract class "ImplementGoodHuman" with abstract attributes "attr1", "attr2" and "attr3"  [abstract]
error: Cannot instantiate abstract class "ImplementBadHuman" with abstract attributes "attr1", "attr2" and "attr4"  [abstract]
error: Cannot instantiate abstract class "RealJumpoSmim" with abstract attributes "jump" and "smim"  [abstract]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment