Last week I started with classes and the basic things you need to know. In this post I explain duck typing and inheritance.
This post is part of my journey to learn Python. You can find the other parts of this series here.
Polymorphism & duck typing
Python uses the concept of duck typing for Polymorphism. Polymorphism in object-oriented programming refers to the ability that many different objects can respond to the same method call. All it takes is that it has the same method signature (parameters). In statically compiled languages like C# we need an interface or inheritance to be able to make those calls possible. In Python it is much simpler, thanks to duck typing. Duck typing is named after the duck test:
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. – James Whitcomb Riley
Or in other words: as long as you have the right methods on your class, we do not care what class you call that method on.
It is a lot simpler to show you some code (this time massively inspired by Gaurav). Given we have a Duck, a Mallard (a special duck) and an Eagle class:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Duck: def quack(self): print("Quack") class Mallard: def quack(self): print("Quaaack") class Eagle: def fly(self): print("fly away") |
We can define a function that expects a “bird” without limiting it to an actual class:
1 2 |
def make_it_quack(bird): bird.quack() |
If we call this method for an instance of Duck and Mallard, everything works as expected then both classes implement quack():
1 2 3 4 5 6 7 |
>>> duck = Duck() >>> make_it_quack(duck) Quack >>> mallard = Mallard() >>> make_it_quack(mallard) Quaaack |
However, the Eagle does not have the quack() method and so it raises an error:
1 2 3 4 5 6 7 8 |
>>> eagle = Eagle() >>> make_it_quack(eagle) Traceback (most recent call last): File "D:/Python/duck_typing.py", line 27, in <module> make_it_quack(eagle) File "D:/Python/duck_typing.py", line 17, in make_it_quack bird.quack() AttributeError: 'Eagle' object has no attribute 'quack' |
The power in duck typing is in all the code we do not need to write. We just need a class with the right method signatures we are going to call – all other parts do not matter. We do not need subclasses and inheritance; we just need that one method. This allows us to combine classes that we never thought of to work together.
Inheritance
Inheritance means that we can derive a subclass form another class. This is especially helpful when we want to reuse behaviour or need little changes for in an algorithm.
In this example (inspired by RealPython) I use Employee as a base class while HourlyEmployee and SalaryEmployee are subclasses:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Employee: def __init__(self, id, name): self.id = id self.name = name def payroll_for(self): return f"Employee #{self.id} {self.name}" class SalaryEmployee(Employee): def __init__(self, id, name, weekly_salary): super().__init__(id, name) self.weekly_salary = weekly_salary def calculate_payroll(self): return self.weekly_salary class HourlyEmployee(Employee): def __init__(self, id, name, hours_worked, hour_rate): super().__init__(id, name) self.hours_worked = hours_worked self.hour_rate = hour_rate def calculate_payroll(self): return self.hours_worked * self.hour_rate |
The algorithm to calculate the payroll is in the function calculate_payroll_for_all()
. This function uses the calculate_payroll()
methods in the subclasses and the payroll_for()
method in the base class. That shows you how the things that are the same can be done only once while the parts that are different are done differently in their subclass.
1 2 3 4 |
def calculate_payroll_for_all(employees): for employee in employees: pay = employee.calculate_payroll() print(f"Pay {pay} to {employee.payroll_for()}") |
We now can instantiate two employees (Mark and Frank) and let the payroll calculation run:
1 2 3 4 5 |
>>> mark = HourlyEmployee(1, "Mark", 40, 100) >>> frank = SalaryEmployee(2, "Frank", 2586) >>> calculate_payroll_for_all([mark, frank]) Pay 4000 to Employee #1 Mark Pay 2586 to Employee #2 Frank |
This example on inheritance shows nicely why duck typing is so often preferred. We can reach nearly the same thing but need to write a lot more code when we use inheritance.
Next
Modules are a great help to organise our code. But as soon as our application grows, they are not enough. It is now time to look at packages.
2 thoughts on “Python Friday #10: Classes (Part 2)”