“If it walks like a duck and quacks like a duck, then it must be a duck.” This is the core philosophy behind Python’s type system. We don’t care what an object is — we care what it can do.
What Is Duck Typing?
In languages like Java, we need to explicitly declare that a class implements an interface. In Python, we just use the object. If it has the method we need, it works.
class Duck:
def quack(self):
print("Quack!")
class Person:
def quack(self):
print("I'm quacking like a duck!")
def make_it_quack(thing):
thing.quack() # we don't check the type — just call the method
make_it_quack(Duck()) # Quack!
make_it_quack(Person()) # I'm quacking like a duck!
make_it_quack doesn’t ask “are you a Duck?” — it just asks “can you quack?” That’s duck typing.
EAFP vs LBYL
Python’s duck typing culture leads to a coding style called EAFP — “Easier to Ask Forgiveness than Permission.” Instead of checking if something is possible before doing it, we just try and handle the failure.
# LBYL (Look Before You Leap) — non-Pythonic
if hasattr(obj, "quack"):
obj.quack()
# EAFP (Easier to Ask Forgiveness) — Pythonic
try:
obj.quack()
except AttributeError:
print("This object can't quack")
EAFP is preferred because it’s faster in the common case (no extra check) and avoids race conditions.
Python’s Built-in Protocols
Python already uses duck typing everywhere through implicit “protocols.” If our object has the right methods, it works with built-in features:
- Iterable — has
__iter__()→ works withforloops - Callable — has
__call__()→ can be called like a function - Context Manager — has
__enter__()and__exit__()→ works withwith - Subscriptable — has
__getitem__()→ supportsobj[key] - Comparable — has
__eq__(),__lt__(), etc. → works with==,< - Hashable — has
__hash__()→ can be used as dict key or in sets
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
# Works with for loops — because it has __iter__ and __next__
for num in Countdown(3):
print(num) # 3, 2, 1
We didn’t inherit from any base class. We just implemented the right methods, and Python’s for loop works with it.
typing.Protocol: Explicit Structural Typing
Since Python 3.8, the typing module gives us Protocol — a way to formally define what methods an object should have, without requiring inheritance.
from typing import Protocol
class Renderable(Protocol):
def render(self) -> str: ...
class HTMLWidget:
def render(self) -> str:
return "<div>Widget</div>"
class JSONData:
def render(self) -> str:
return '{"key": "value"}'
def display(item: Renderable) -> None:
print(item.render())
display(HTMLWidget()) # works — has render()
display(JSONData()) # works — has render()
HTMLWidget and JSONData never mention Renderable. They just happen to have a render() method. The type checker sees the match and approves.
runtime_checkable
By default, Protocol only works for static type checking (mypy). If we want isinstance() checks at runtime, we add @runtime_checkable:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closeable(Protocol):
def close(self) -> None: ...
import io
f = io.StringIO()
print(isinstance(f, Closeable)) # True — StringIO has close()
print(isinstance(42, Closeable)) # False — int doesn't have close()
Note: runtime_checkable only checks if the methods exist, not their signatures. It’s a quick duck-type check, not a full type validation.
Protocol vs ABC (Abstract Base Classes)
Both define interfaces, but they work differently:
- ABC — requires explicit inheritance (
class MyClass(MyABC)). It’s nominal typing — “I declare that I implement this.” - Protocol — no inheritance needed. It’s structural typing — “I have the right methods, so I match.”
from abc import ABC, abstractmethod
# ABC approach — must inherit
class Drawable(ABC):
@abstractmethod
def draw(self): ...
class Circle(Drawable): # must explicitly inherit
def draw(self):
print("Drawing circle")
# Protocol approach — no inheritance
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Square: # no inheritance needed
def draw(self):
print("Drawing square")
Use ABCs when we own the class hierarchy and want to enforce a contract. Use Protocols when we want to accept any object that has the right shape — especially useful for third-party code we can’t modify.
In simple language, duck typing is Python saying “show me what you can do, not who you are.” Protocols take this idea and give it structure — we can describe the shape we need without forcing anyone to inherit from our classes.