.. role:: python(code) :language: python .. index:: protocol behavioral interface .. `protocol `_ .. _`sec:protocol`: Protocols --------- :python:`typing.Protocol` allows to define your own abstract type, to enforce a custom `behavioral interface `_. `comparison with ABC `_ .. admonition:: Exercise (easy) Use a protocol for a math object that has a ``norm()`` method. .. code-block:: python :linenos: from collections.abc import ... from typing import Protocol import math class Normable(Protocol): def norm(self) -> float: ... def is_unit(obj: ...) -> bool: return math.isclose(obj.norm(), 1.0) class Vector: def __init__(self, coefficents: ...) -> None: self._coefficients: list[float] = list(coefficents) def norm(self) -> float: return math.sqrt(sum(coefficient**2 for coefficient in self._coefficients)) .. _`ex:protocol`: .. collapse:: Correction It is not mandatory to inherit from :python:`Normable` in the definition of :python:`Vector`. Doing so enables to trigger an error at the line of vector instantiation if the :python:`Vector` class has not implemented the :python:`norm` method. .. code-block:: python :linenos: from collections.abc import Sequence from typing import Protocol import math class Normable(Protocol): def norm(self) -> float: ... def is_unit(obj: Normable) -> bool: return math.isclose(obj.norm(), 1.0) class Vector: def __init__(self, coefficents: Sequence[float]) -> None: self._coefficients: list[float] = list(coefficents) def norm(self) -> float: return math.sqrt(sum(coefficient**2 for coefficient in self._coefficients)) class VectorNoNorm: def __init__(self, coefficents: Sequence[float]) -> None: self._coefficients: list[float] = list(coefficents) class VectorNoNorm_(Normable): def __init__(self, coefficents: Sequence[float]) -> None: self._coefficients: list[float] = list(coefficents) vector = Vector((3.0, 4.0)) vector_ = Vector({3.0, 4.0}) # not ok is_unit(vector) is_unit(2) # not ok vector_no_norm = VectorNoNorm((3.0, 4.0)) is_unit(vector_no_norm) # not ok VectorNoNorm_((3.0, 4.0)) # not ok