Polymorphism
One of the great benefits of object-oriented programming is polymorphism: different types of objects sharing the same interface.
Polymorphism from inheritance
We've already seen many polymorphic objects, since inheritance almost always leads to polymorphism.
For example, we can call the eat
method on both Panda
object and Lion
objects, passing in the same argument:
broc = Meal("Broccoli", "veggie")
pandey = Panda("Pandeybear", 6)
mufasa = Lion("Mufasa", 12)
pandey.eat(broc)
mufasa.eat(broc)
Now, the two objects may react differently to that method, due to differing implementations. The panda might say "Mmm yummy" and the lion might say "Gross, veggies!". But the point of polymorphism is that the interface was the same - the method name and argument types - which meant we could call that interface without knowing the exact details of the underlying implementation.
When we have a list of polymorphic objects, we can call the same method on each object even if we don't know exactly what type of object they are.
Consider this function that throws an animal party, making each animal in a list interact_with
the other animals in the list:
def partytime(animals):
for i in range(len(animals)):
for j in range(i + 1, len(animals)):
animals[i].interact_with(animals[j])
That list of animals
can contain any Animal
instance, since they all implement interact_with
in some way.
jane_doe = Rabbit("Jane Doe", 2)
scar = Lion("Scar", 12)
elly = Elephant("Elly", 5)
pandy = Panda("PandeyBear", 4)
partytime([jane_doe, scar, elly, pandy])
That right there is the power of polymorphism.
Polymorphism from method interfaces
We've also seen a lot of polymorphism in the built-in Python object types, even though we haven't called it that.
For example, consider how many types we can pass to the len
function:
len([1, 2, 3])
len("ahoy there!")
len({"CA": "Sacramento", "NY": "Albany"})
len(range(5))
The len()
function is able to report the length of lists, strings, dicts, and ranges. All of those inherit from object
, but len()
doesn't work on any old object. So why does it work for those types?
It's actually because the list
, str
, dict
, and range
classes each define a method named __len__
that reports their length using various mechanisms. The global len()
function works on any object that defines a __len__
method and returns a number. That means we can even make len()
work on our own user-defined classes.
This type of polymorphism in a language is also known as duck typing: if it walks like a duck and quacks like a duck, then it must be a duck. 🦆The len()
function doesn't care exactly what kind of object it's passed: if it has a __len__
method and that method takes 0 arguments, then that's good enough for Python.
Many languages are more strict than Python, and possibly for good reason, since you can get yourself in trouble with loosey-goosey duck typing. But you can also write highly flexible code. Tread carefully in these polymorphic waters!