Duck Typing & Protocols
"If it walks like a duck and quacks like a duck, it's a duck." Python cares about what an object can do, not what it is.
Duck Typing
class Dog:
def speak(self): return "Woof!"
class Cat:
def speak(self): return "Meow!"
class Robot:
def speak(self): return "Beep boop."
def make_noise(thing):
# No isinstance check needed — just call .speak()
print(thing.speak())
for obj in [Dog(), Cat(), Robot()]:
make_noise(obj)typing.Protocol (Python 3.8+)
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
def get_color(self) -> str: ...
class Circle:
def draw(self): print("Drawing circle")
def get_color(self): return "red"
class Triangle:
def draw(self): print("Drawing triangle")
def get_color(self): return "blue"
def render(shape: Drawable) -> None:
print(f"Color: {shape.get_color()}")
shape.draw()
# Both work without inheriting from Drawable:
render(Circle())
render(Triangle())Checking Protocol (runtime_checkable)
from typing import runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self) -> None: ...
print(isinstance(Circle(), Drawable)) # True