ProtocolsΒΆ

typing.Protocol allows to define your own abstract type, to enforce a custom behavioral interface.

comparison with ABC

Exercise (easy)

Use a protocol for a math object that has a norm() method.

 1from collections.abc import ...
 2from typing import Protocol
 3import math
 4
 5
 6class Normable(Protocol):
 7    def norm(self) -> float: ...
 8
 9
10def is_unit(obj: ...) -> bool:
11    return math.isclose(obj.norm(), 1.0)
12
13
14class Vector:
15    def __init__(self, coefficents: ...) -> None:
16        self._coefficients: list[float] = list(coefficents)
17
18    def norm(self) -> float:
19        return math.sqrt(sum(coefficient**2 for coefficient in self._coefficients))
Correction

It is not mandatory to inherit from Normable in the definition of Vector. Doing so enables to trigger an error at the line of vector instantiation if the Vector class has not implemented the norm method.

 1from collections.abc import Sequence
 2from typing import Protocol
 3import math
 4
 5
 6class Normable(Protocol):
 7    def norm(self) -> float: ...
 8
 9
10def is_unit(obj: Normable) -> bool:
11    return math.isclose(obj.norm(), 1.0)
12
13
14class Vector:
15    def __init__(self, coefficents: Sequence[float]) -> None:
16        self._coefficients: list[float] = list(coefficents)
17
18    def norm(self) -> float:
19        return math.sqrt(sum(coefficient**2 for coefficient in self._coefficients))
20
21class VectorNoNorm:
22    def __init__(self, coefficents: Sequence[float]) -> None:
23        self._coefficients: list[float] = list(coefficents)
24
25
26class VectorNoNorm_(Normable):
27    def __init__(self, coefficents: Sequence[float]) -> None:
28        self._coefficients: list[float] = list(coefficents)
29
30vector = Vector((3.0, 4.0))
31vector_ = Vector({3.0, 4.0}) # not ok
32
33is_unit(vector)
34is_unit(2)  # not ok
35
36vector_no_norm = VectorNoNorm((3.0, 4.0))
37is_unit(vector_no_norm) # not ok
38
39VectorNoNorm_((3.0, 4.0)) # not ok