AiTechWorlds
AiTechWorlds
There is an old American saying: "If it walks like a duck and quacks like a duck, it's a duck."
Suppose you run a nature documentary and you need something that quacks for a sound effect. You don't care whether you have a real Mallard duck, a rubber toy duck, or a trained parrot that learned to quack. If the thing in front of you can quack, it works for your purposes. You don't ask for its species certificate.
Python thinks the same way about objects. It does not ask "are you the right type?" before calling a method. It simply tries to call the method. If the method exists and works, great. If not, you get an AttributeError. Python doesn't check ancestry — it checks capability.
This philosophy is called duck typing.
In Java or C++, to guarantee that a class can be used in a particular role, you declare that it implements an interface:
// Java
interface Serializable {
String serialize();
}
class User implements Serializable {
public String serialize() { return "{\"user\": \"Alice\"}"; }
}
In Python, no such declaration is required. If an object has a serialize() method, it works — full stop. No ceremony, no boilerplate.
# Python — no interface declaration needed
class User:
def serialize(self):
return '{"user": "Alice"}'
class Product:
def serialize(self):
return '{"product": "Widget", "price": 9.99}'
class LegacyRecord:
# Written years ago, knows nothing about your new code
def serialize(self):
return "old|format|data"
def save_to_disk(item):
data = item.serialize() # Duck typing: just call the method
print(f"Saving: {data}")
save_to_disk(User())
save_to_disk(Product())
save_to_disk(LegacyRecord())
Output:
Saving: {"user": "Alice"}
Saving: {"product": "Widget", "price": 9.99}
Saving: old|format|data
save_to_disk never checks isinstance(item, SomeInterface). It trusts that item has serialize(). If it does, everything works — regardless of whether User, Product, and LegacyRecord share any inheritance relationship.
for Loop: Duck Typing Under the HoodPython's for loop is itself a perfect example of duck typing. It does not require an object to inherit from some Iterable base class. It simply checks for two methods:
__iter__(self) — return an iterator object__next__(self) — return the next value, raise StopIteration when doneAny object that provides these two methods can be used in a for loop:
class Countdown:
"""An iterable that counts down from n to 1."""
def __init__(self, start):
self.current = start
def __iter__(self):
return self # The object itself is the iterator
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for number in Countdown(5):
print(number, end=" ")
print()
Output:
5 4 3 2 1
Countdown inherits from nothing special. Python's for loop simply called __iter__() to get an iterator, then called __next__() repeatedly until StopIteration was raised.
When duck typing is too permissive — when you want to enforce that subclasses implement specific methods — Python's abc module provides Abstract Base Classes:
from abc import ABC, abstractmethod
class Serializable(ABC):
@abstractmethod
def serialize(self) -> str:
"""Every subclass MUST implement this."""
...
@abstractmethod
def deserialize(self, data: str) -> None:
"""Every subclass MUST implement this."""
...
class UserRecord(Serializable):
def __init__(self, name: str):
self.name = name
def serialize(self) -> str:
return f'{{"name": "{self.name}"}}'
def deserialize(self, data: str) -> None:
import json
self.name = json.loads(data)["name"]
# This would raise TypeError at instantiation time:
# class BadRecord(Serializable):
# pass # Missing required methods
# b = BadRecord() # TypeError: Can't instantiate abstract class BadRecord
# # without an implementation for abstract methods ...
u = UserRecord("Alice")
print(u.serialize())
u.deserialize('{"name": "Bob"}')
print(u.name)
Output:
{"name": "Alice"}
Bob
ABCs catch missing methods at object creation time — before any method is called, you get a clear TypeError.
typing.Protocol: Structural SubtypingPython 3.8 introduced typing.Protocol — a way to define an interface for type checkers without requiring inheritance:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Renderable(Protocol):
def render(self) -> str:
... # Just a signature — no implementation
def get_title(self) -> str:
...
class HtmlPage:
def render(self) -> str:
return "<html><body>Hello</body></html>"
def get_title(self) -> str:
return "Home Page"
class PdfDocument:
def render(self) -> str:
return "%PDF-1.4 ..."
def get_title(self) -> str:
return "Annual Report"
def publish(item: Renderable) -> None:
print(f"Title: {item.get_title()}")
print(f"Content: {item.render()[:40]}")
publish(HtmlPage())
publish(PdfDocument())
# With @runtime_checkable, isinstance checks work:
print(isinstance(HtmlPage(), Renderable)) # True
Output:
Title: Home Page
Content: <html><body>Hello</body></html>
Title: Annual Report
Content: %PDF-1.4 ...
True
HtmlPage and PdfDocument never imported or inherited from Renderable. They simply happened to have the right method signatures. The @runtime_checkable decorator allows isinstance() checks to verify protocol compliance at runtime.
| Feature | Java / C++ Interface | Python ABC | Python Protocol |
|---|---|---|---|
| Requires explicit declaration | Yes — implements Interface | Yes — inherit from ABC | No — structural matching |
| Enforced at | Compile time | Object instantiation | Type checker / runtime check |
| Allows existing classes | No — must retrofit | No — must retrofit | Yes — works on unmodified classes |
Runtime isinstance check | Yes | Yes | Yes (with @runtime_checkable) |
| Best use case | Strict OOP languages | Enforce contract in Python class hierarchy | Type-hinting for duck-typed APIs |
| Situation | Recommended Approach |
|---|---|
| Small script, few classes, clear intent | Plain duck typing — no formality needed |
| Shared codebase, team needs contracts | ABC — catches missing methods early |
| Library with public API, type hints matter | typing.Protocol — flexible and tool-friendly |
| Integrating third-party classes you can't modify | Protocol — no inheritance required |
| Maximum strictness, mandatory method list | ABC with @abstractmethod |
for, with, and arithmetic operators all rely on duck typing via dunder methods (__iter__, __enter__, __add__, etc.).typing.Protocol defines structural interfaces that work without inheritance — ideal for type-checking duck-typed code.Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises