$ cat test.py
from dataclasses import dataclass
from typing import Protocol
class Ducky(Protocol):
def quack(self) -> None:
"Quack."
class FringeScientist:
def quack(self) -> bool:
return True
@dataclass
class Duck:
quiet: bool = False
def quack(self) -> None:
print("Quack." if self.quiet else "QUACK!")
def duck_war(aggressor: Ducky, defender: Ducky) -> None:
aggressor.quack()
defender.quack()
print("The only winning move is not to play.")
duck = Duck()
asshat = FringeScientist()
duck_war(duck, asshat)
$ mypy test.py
test.py:32: error: Argument 2 to "duck_war" has incompatible type "FringeScientist"; expected "Ducky" [arg-type]
test.py:32: note: Following member(s) of "FringeScientist" have conflicts:
test.py:32: note: Expected:
test.py:32: note: def quack(self) -> None
test.py:32: note: Got:
test.py:32: note: def quack(self) -> boolThat's before we even get into the philosophical issue of, who are you, the lowly library implementor, to say those aren't something appropriate to abstract over in my application?
Scientist.quack really shouldn't be returning plain strings, and neither should ducky.quack if the data in them is contextually different.
Things should only become basic types at the last possible moment.
This is one of the reasons it is so rare to actually have confusable types in a real program.
The interfaces matched, the compiler accepted it, but the application blew up at runtime.
Extending this, if the class has a `partial_fit` method, sometimes I want to use that, and fallback to `fit` when it doesn't.
And the official one obviously couldn't be used.