SyntaxStudy
Sign Up
Python Duck Typing & Protocols
Python Intermediate 10 min read

Duck Typing & Protocols

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
Example
from typing import Protocol, runtime_checkable

@runtime_checkable
class Summable(Protocol):
    def total(self) -> float: ...

class Cart:
    def __init__(self, items): self.items = items
    def total(self): return sum(self.items)

class Invoice:
    def __init__(self, lines): self.lines = lines
    def total(self): return sum(l["amount"] for l in self.lines)

def print_total(s: Summable):
    print(f"Total: ${s.total():.2f}")

print_total(Cart([9.99, 14.50, 3.25]))
print_total(Invoice([{"amount": 50.0}, {"amount": 25.0}]))

print(isinstance(Cart([]), Summable))  # True
Pro Tip

Protocols give you the benefits of static type checking without requiring inheritance. Your class just needs to implement the right methods — no coupling needed.