Definition
You are writing a function that has to make an object speak. Which objects is it allowed to receive? Many languages demand an answer written in advance: a precise class, or an interface to implement. Python answers differently, and its answer has a name: duck typing.
The rule is that Python judges an object on what it can do, not on the class it descends from. The original phrasing says it better than a definition: if an object walks like a duck and quacks like a duck, treat it as a duck. No declaration up front, no interface to sign: the method being called only has to exist on the object at the moment it is called.
Two classes with no family tie whatsoever, and one function that treats them exactly alike:
class Duck:
def quack(self):
return "quack"
class Robot:
def quack(self):
return "synthetic quack"
def make_it_speak(animal):
# No check at all: we call, and we will see.
print(animal.quack())
make_it_speak(Duck())
make_it_speak(Robot())The make_it_speak function never asks for the type of what it receives: it calls, and Python looks the name quack up on the object at the very last moment. An object written elsewhere, by somebody who had never heard of that function, will therefore work with it without a single line of adaptation: compatibility is not declared, it is observed.
What Python checks, and when
If nothing is checked in advance, what is checked, and when? Many languages verify compatibility before execution, on a hierarchy declared in advance; Python verifies it during execution, on the names actually present. The table puts them face to face, Java or C# on the left, Python on the right.
| Nominal typing | Duck typing |
|---|---|
| The question asked is "what type is this object" | The question asked is "does this object have what it takes" |
| The check happens before the program runs | The check happens on the line that calls |
| You must inherit or implement an interface | You only need to carry the right method name |
| A mismatch blocks compilation | A mismatch raises a TypeError |
Moving the check has one very concrete consequence: a class does not need to know the classes that will use it, nor the other way round. Two libraries built separately plug into each other as long as they agree on method names, with no shared dependency to install.
Protocols, the contracts nobody signs
Duck typing is not a tolerance granted to your code: Python applies it to itself first. Its own constructs do not demand a precise type, they demand a magic method, that is, a method whose name is wrapped in double underscores. Here are the four you meet earliest.
| What you want to do | The method to write |
|---|---|
| Pass the object to len | __len__ |
| Walk it with for | __iter__, which makes it an iterable |
Open it inside a with block | __enter__ and __exit__ |
| Print it readably | __str__ |
Nothing to inherit, nothing to declare: writing the method is enough. It is why a function that accepts "a file" really accepts any object carrying the read and write methods, and why a fake file held in memory replaces the real one in tests, without a line of the tested code moving.
Two unrelated classes can carry the same method with a different meaning. An object holding a send method does not necessarily send a message: Python will accept it without blinking and the problem will surface much further away, in a shape that no longer looks like a type error.
The price to pay: the error arrives late
That freedom has a counterpart. Nothing warns that an object is unfit before the line that uses it: missing the expected method, it travels quietly through the intermediate calls and triggers an AttributeError deep in the stack, far from where it was built. Hand a cat to the function from the beginning, and the error message will mention neither the cat nor the function.
class Cat:
def meow(self):
return "meow"
make_it_speak(Cat())
# Nothing flinches until animal.quack() is called
# AttributeError: 'Cat' object has no attribute 'quack'Two habits limit the damage. The first is to document the expectation instead of leaving it to be guessed: a type hint or a docstring naming the required methods makes the problem show up in the editor, before the program runs.
The second is to wrap the call in a try when receiving a loosely shaped object is one of the expected cases. Interrogating the object before using it looks safer, but that caution ends up turning away objects that would have worked perfectly well.
Frequently asked questions
Should the type be checked with isinstance before calling?
Rarely. An isinstance closes the door duck typing has just opened: it turns down a perfectly compatible object for the sole reason that it does not belong to the right family. Python culture prefers attempting the call and catching the failure. The check keeps its place where data enters the program from outside.
Does duck typing make type hints useless?
No, the two go together. The typing module offers Protocol, which describes the expected methods without forcing any inheritance: the checker gains a guarantee and the caller keeps the freedom to supply any conforming object. A tool such as mypy reads those protocols and flags the incompatible object before the program even runs.
What is the difference with inheritance?
Inheritance shares code and creates a family: a subclass receives the methods of its parent class. Duck typing shares nothing but a vocabulary: two objects with nothing in common become interchangeable because they answer to the same method name. The Python course shows where the line falls between the two.