225 – Protocols and bounded type variables

225 – Protocols and bounded type variables#

Type variables can be bound to a given type to only allow the type and its subclasses. This is especially useful when used with protocols:

from typing import Protocol

class Diver(Protocol):
    def dive(self) -> None: ...

class Penguin:
    def dive(self) -> None: ...

The class Penguin satisfies the protocol Diver. Now, a function that uses the protocol Diver as the type for an argument can accept penguins:

def proto(obj: Diver) -> Diver:
    # ...
    obj.dive()
    # ...
    return obj

However, since the function is typed to return a Diver, you lose information about the return value even knowing you’re returning the same argument you passed in:

p = Penguin()
reveal_type(p)  # Penguin
reveal_type(proto(p))  # Diver

On the other hand, you can bind a type variable to the protocol and use it instead:

def tvar[D: Diver](obj: D) -> D:
    # ...
    obj.dive()
    # ...
    return obj

The notation [D: Diver] says that the type variable D is only good for the class Diver and its subclasses, which incidentally are the classes that satisfy the protocol. This preserves all the information:

p = Penguin()
reveal_type(p)  # Penguin
reveal_type(tvar(p))  # Penguin