Skip to content
wannacode

Interfaces as abstract classes

Problem

Salaries and invoices

At the end of the month the company pays two kinds of things: the employees' salaries and the suppliers' invoices. You want one list and one loop: who gets paid, how much, and the total at the end.

An invoice is not an employee. Making it inherit from Employee is wrong. The sentence "an invoice is an employee" is not true, and the invoice would also get a name, a salary and a raise.

A common parent Payment with fields and code is not the right answer either: employees and invoices have no shared data and no shared code. All they have in common is that they can be paid.

The idea

An interface: a "can do" contract

An interface is a list of methods without bodies: what a class must be able to do, but not how. Python has no separate word for it. You write the contract as an abstract class in which all methods are abstract:

from abc import ABC, abstractmethod
class Payable(ABC):
@abstractmethod
def amount_due(self):
pass
@abstractmethod
def payee(self):
pass
A class that accepts the contract implements the interface and writes each of its methods:
class Invoice(Payable):
def __init__(self, supplier, amount):
self._supplier = supplier
self._amount = amount
def amount_due(self):
return self._amount
def payee(self):
return self._supplier
power = Invoice("Power company", 340)
print(f"{power.payee()}: €{power.amount_due()}")
Output
Power company: €340

Payable sits in the parentheses like a parent. Python does not tell a parent with code apart from a contract parent: the difference is that Payable has no fields and no method bodies. The check works like it does for an abstract class: you cannot create objects from Payable. Payable() stops with

TypeError: Can't instantiate abstract class Payable without an implementation for abstract methods 'amount_due', 'payee'

See it

One loop for everything that gets paid

The employee accepts the contract too. Its amount is the monthly salary:

class Employee(Payable):
# ... _name, _salary, __init__, monthly_pay()
def amount_due(self):
return self.monthly_pay()
def payee(self):
return self._name

Now both the employees and the invoices are Payable: both classes have it as a parent. The list has no type, but everything in it keeps the contract, so the loop can call amount_due() and payee() on each one.

payments = [
Employee("Ivan", 2000),
Manager("Maria", 3000, 500),
Invoice("Power company", 340),
Invoice("Landlord", 1800),
]
total = 0
for p in payments:
print(f"{p.payee()}: €{p.amount_due()}")
total += p.amount_due()
print(f"Total: €{total}")
Output
Ivan: €2000
Maria: €3500
Power company: €340
Landlord: €1800
Total: €7640

The manager writes nothing new. From Employee it inherits both the contract and amount_due(). And amount_due() calls monthly_pay(), which the manager has overridden. That is why Maria gets €3500.

payments = [
Employee("Ivan", 2000),
Manager("Maria", 3000, 500),
Invoice("Power company", 340),
]
total = 0
for p in payments:
total += p.amount_due()
print(f"Total: €{total}")
Step by step, in words
  1. The list holds three objects of three classes. They are all Payable, and the arrows reach only their contract.
  2. total starts at 0.
  3. p points to the employee: amount_due() returns the salary, 2000.
  4. The manager is an employee, so the manager can be paid too. Salary plus bonus: 3500.
  5. The invoice is not an employee, but it keeps the same contract: 340.
  6. The loop does not know the classes. It only knows that each one can be paid.

Try it

What is the amount?

What will the program print? The classes are from the card above.

p = Manager("Maria", 3000, 500)
print(p.amount_due())
Answer

The idea

One parent, many contracts

Accounting also wants the invoices in CSV format: one "supplier;amount" line each. That is a second contract. In Python a class can have as many parents as it wants, separated by commas, so contracts are just more parents:

class Exportable(ABC):
@abstractmethod
def to_csv(self):
pass
class Invoice(Payable, Exportable):
# ... as before
def to_csv(self):
return f"{self._supplier};{self._amount}"
for_accounting = [Invoice("Power company", 340), Invoice("Landlord", 1800)]
for e in for_accounting:
print(e.to_csv())
Output
Power company;340
Landlord;1800

If the class has both a parent and contracts, the parent comes first:

class Manager(Employee, Exportable):

The manager then writes to_csv() as well, and is an Employee, a Payable and an Exportable at the same time. The order in the parentheses matters: when two parents have a method with the same name, the first one wins.

There can be many parents, but it is best to have only one with code and data. The rest are contracts: only promises, no fields.

Try it

Find the bug

Invoice("Power company", 340) stops with TypeError. Which line has the bug?

Pick the line with the bug.

Try it

What is missing?

class Invoice():
    # ...

The idea

Abstract class or interface?

In Python both are abstract classes: a contract is an abstract class with no fields and no code. The difference is not in the language but in what they say:

Abstract class with code Contract
Says "X is a Y" "X can"
How many per class as many as you want, but one is best as many as you want
Fields and __init__ yes no
Shared code yes hardly any
  • An abstract class, when the classes below it are one kind of thing and share code and data: shapes have a name and a description, employees have a name and a salary.
  • A contract, when the classes are different but need a common ability: an employee and an invoice "can be paid", a bird and a plane "can fly".
They are often used together: Employee is the parent of the manager and the intern, and at the same time it implements Payable.
More precisely, if you are curious
  • A contract can also have a default method: an ordinary method with a body next to the abstract ones. Classes get it ready-made, and they can also override it. That way you can add a new method to the contract without breaking the classes that already fulfill it:
class Flyable(ABC):
@abstractmethod
def fly(self):
pass
def glide(self):
print("Gliding through the air")
class Bird(Flyable):
def fly(self):
print("The bird flaps its wings")
class Plane(Flyable):
def fly(self):
print("The plane takes off")
flyers = [Bird(), Plane()]
for f in flyers:
f.fly()
f.glide()
Output
The bird flaps its wings
Gliding through the air
The plane takes off
Gliding through the air
  • There is a second way to write a contract: typing.Protocol. The class then does not write the contract in its parentheses. As long as it has the methods, it fits, just like in JavaScript. Python itself does not check the protocol: the ticket without amount_due() passes as a Payable. A separate type checker checks it before the program runs. With @runtime_checkable and isinstance (the lesson "What is it really?") you can ask whether the object has the methods:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Payable(Protocol):
def amount_due(self): ...
def payee(self): ...
class Invoice:
def __init__(self, supplier, amount):
self._supplier = supplier
self._amount = amount
def amount_due(self):
return self._amount
def payee(self):
return self._supplier
class Ticket:
def payee(self):
return "Cinema"
p: Payable = Ticket()
print(isinstance(Invoice("Landlord", 1800), Payable))
print(isinstance(p, Payable))
Output
True
False

Exercise

Contracts

Solve the tasks in your editor. In Python each "interface" is an abstract class (ABC) with only abstract methods.

1. Horn. An interface Honkable with a method honk(). SimpleCar from the module "Classes and objects" implements it.

Output
Beep beep!

2. Details. An interface Printable with a method print_details(). Implement it in Person, and have Student (a child of Person) override it and add the grade. Put both of them in one list.

Output
Name: Georgi, age: 45
Name: Elena, age: 19
Average grade: 5.5

3. Dance. An interface Danceable with a method dance(). DanceMove (a name and a duration) implements it.

Output
Dancing salsa for 30 seconds

4. DJ. A class Song with a title. An interface Playable with a method play(). DJ implements it and has a method play_song(song) that remembers the song and plays it. If there is no song yet, play() says so.

Output
No song selected
Playing 'Bohemian Rhapsody'

5. The pond. Interfaces Swimmable with swim() and Flyable with fly(). Fish swims. Duck both swims and flies.

Output
The fish swims.
The duck swims.
The duck flies.

More tasks

  • Discount. An interface Discountable with a method that returns the price after the discount. Implement it in Product (10% off) and in Subscription (25% off for students). Work out the total of a cart with a €50 product and a €12 student subscription: €45 + €9 = €54.
  • CSV for everyone. Make Employee implement Exportable too, with a "name;monthly salary" line. Put employees and invoices in one list.

Test

Check yourself

1. How many contracts (abstract classes) can one class fulfill?
2. Payable inherits from ABC and has two abstract methods. What happens on Payable()?
3. Invoice(Payable) writes amount_du() instead of amount_due(). When will you find out?
4. Who checks a contract written with typing.Protocol?
5. Employees and invoices have to be paid in one loop. They share no code. What fits better?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·