Skip to content

#10: Classes (Part 2)

Last week I started with classes and the basic things you need to know. In this post I explain duck typing and inheritance.

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:

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:

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:

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 and packages are needed.