If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
This means that the type of the object, i.e, its own class, or the class it inherits from doesn’t matter, the interpreter only cares about the interfaces of the object.
class Duck: def quack_and_swim(self): print('quack and swim') class Bird: def quack_and_swim(self): print('quack and swim') class Frog: def ribbit_and_swim(self): print('ribbit and swim') def check_duck(animal): animal.quack_and_swim() if __name__ == '__main__': duck = Duck() bird = Bird() frog = Frog check_duck(duck) check_duck(bird) check_duck(frog)
The output of the code above is:
quack and swim quack and swim Traceback (most recent call last): File "", line 7, in File "", line 2, in is_duck AttributeError: type object 'Frog' has no attribute 'quack_and_swim'
The first and second objects
bird could successfully respond to the of
quack_and_swim() while the
frog object couldn’t and throws an exception.
In terms of code, the classes
Bird are not related at all, they don’t share a parent class through inheritance, but they have the same interface for the function
The type of the object is not important, the interface of the object when you use it is what matters.
In the end, if the object behaves like a duck, for practical purposes, it is a duck, no matter what bird it is, as long as it has the right interface.
But what do we do about the thrown exception?
Since we don’t check the type of the object or if it has certain attributes, the Pythonic way to handle any exception is to use a
try: check_duck(frog) except AttributeError as error: print(error)
The treated exception above will output:
Traceback (most recent call last): File "", line 2, in NameError: name 'check_duck' is not defined
So we always assume the object has the method or attribute we need, if it doesn’t, we catch the exception with the
try block and treat it accordingly.
If you had any trouble following this article, I recommend this series of articles about Object-Oriented Programming: