parent :< child
✅
child :< parent
❌
i.e (fn args)
def eat(anima: Animal): ...
eat(Dog())
def bark(dog: Dog): ...
bark(Animal())
parent :< child
❌
child :< parent
✅
i.e (return types):
def debug_animal_create(fn: Callable[[], Animal]) -> Animal:...
def get_dog() -> Dog: ...
def get_zebra() -> Zebra: ...
debug_animal_create(get_dog) # Callable[[], Animal] :< Callable[[], Dog]
debug_animal_create(get_zebra) # callable[[], Animal] :< Callable[[], Zebra]
def get_animal() -> Animal: ...
def debug_zebra_create(fn: Callable[[], Zebra]) -> Zebra:...
debug_zebra_create(get_animal) # ❌ Callable[[], Zebra] !:< Callable[[], Animal]
parent :< child
❌
child :< parent
❌
i.e (mutable containers):
dogs = list[Dog] = [Dog()]
animals: list[Animals] = [Fish()]
animals.append(Dog()) # ✅ dog :< animal
animals = dogs # ❌ list[Dog] !:< list[Animal]
dogs = animals # ❌