contra-variance
from typing import Callable class Food: ... class Meat(Food): ... class Animal: def eat(self, food: Food): ... class Dog(Animal): def eat(self, food: Food): ... def dog(self): print('Dog', self) a: Animal = Animal() d: Dog = Dog() def animal_run(animal: Animal) -> None: # Callable[[Animal], None] print('An animal is running!') def dog_run(dog: Dog) -> None: # Callable[[Dog], None] print('A dog is running!') dog.dog() animal_run(a) dog_run(d) # Dog <: Animal # Callable[[Animal], None] <: Callable[[Dog], None] contra-variance def make_dog_run(dog: Dog, run_function: Callable[[Dog], None]) -> None: run_function(dog) lassie: Dog = Dog() make_dog_run(dog=lassie, run_function=animal_run) make_dog_run(dog=lassie, run_function=dog_run)
https://dev.to/daniel1in/python-type-hint-contravariant-covariant-invariant-15lj
from abc import ABCMeta, abstractmethod from typing import TypeVar, Generic class Base: def foo(self): print('foo') class Derived(Base): def bar(self): print('bar') T_contra = TypeVar('T_contra', bound=Base, covariant=True, contravariant=False) class Sink(Generic[T_contra]): @abstractmethod def consume(self, value: T_contra): ... class SinkBase(Sink[Base]): def consume(self, value: Base): value.foo() class SinkDerived(Sink[Derived]): def consume(self, value: Derived): value.bar() def other_func(self): ... base = Base() derived = Derived() sink_base: Sink[Base] = SinkDerived() sink_derived: Sink[Derived] = SinkBase() class Source(Generic[T_contra]): @abstractmethod def generate(self) -> T_contra: ... class SourceBase(Source[Base]): def generate(self) -> Derived: return Derived() class SourceDerived(Source[Derived]): def generate(self) -> Derived: return Derived() source: Source[Base] = SourceDerived() source.generate() source_derived: Source[Derived] = SourceBase() source_derived.generate()
https://blog.daftcode.pl/covariance-contravariance-and-invariance-the-ultimate-python-guide-8fabc0c24278