4.1.2.3 - Inheritance, polymorphism and object relationships (Python)

4.1.2.3 - Inheritance, polymorphism and object relationships (Python)

Learn how specialised classes reuse and override behaviour, how a common operation selects different implementations, and how ownership and interfaces guide class relationships.

Inheritance and overriding

Inheritance models a specialised type that is also a valid instance of a more general type.

Inheritance and overriding

Inheritance lets a subclass acquire attributes and behaviour from a superclass. Overriding gives an inherited method a specialised implementation in the subclass, using the same operation/interface.

A StudentPass is a Pass, so inheritance may fit. A Pass has a holder; a holder is not a kind of pass, so inheritance does not model that relationship. With single inheritance, a class has one direct superclass. In class StudentPass(Pass):, Python looks in StudentPass first for a requested method, then in its base class when needed. super().__init__(...) initialises the inherited state rather than duplicating that code.

Python
class Pass:
    def __init__(self, holder, price):
        self.holder = holder
        self._price = price

    def cost(self):
        return self._price

class StudentPass(Pass):
    def cost(self):
        return self._price // 2

normal = Pass("Leila", 1000)
student = StudentPass("Omar", 1000)
print(normal.holder, normal.cost())
print(student.holder, student.cost())

The output is Leila 1000 and Omar 500. StudentPass inherits the constructor and holder field and overrides cost. Its _price uses the protected-style Python convention from the previous lesson; the runtime does not enforce protected access. A subclass with a new constructor should call the base initialiser if it needs that state. Do not confuse overriding with calling another method, or with overloading several signatures under one name.

Polymorphism

Polymorphism lets a caller use one common operation while the receiving object's concrete type supplies the behaviour.

Polymorphism

Different object types responding to the same operation/interface in their own appropriate ways. With overriding, an instance method call is dispatched to the implementation for the actual object.

A report can loop through several pass objects and call cost() without testing a type code to choose each formula. A superclass reference in a statically typed language can refer to a subclass object; Python names can likewise refer to objects of different concrete classes without an explicit type declaration. The object's actual type determines the overridden implementation. Creating many identical Pass objects is not itself polymorphism: there must be differing implementations behind the common operation.

One call, different costs

{"code":"class Pass:\n def init(self, holder, price):\n self.holder = holder\n self._price = price\n\n def cost(self):\n return self._price\n\nclass StudentPass(Pass):\n def cost(self):\n return self._price // 2\n\npasses = [Pass("Leila", 1000), StudentPass("Omar", 1000)]\nfor travel_pass in passes:\n print(travel_pass.holder, travel_pass.cost())\n","instructions":"Predict and run the two output lines. Add a SeniorPass subclass whose cost returns three quarters of _price using _price * 3 // 4, and add SeniorPass("Rina", 1000) to the list. Run again without changing the reporting loop. Also try a base price of 0. All prices are non-negative integer pence; floor division rounds down.","files":[],"original_filename":"main.py","runtime_profile":"python-console-v1","file_mode":"fresh"}

The new third line is Rina 750; a zero-price object returns 0. The loop remains unchanged because every concrete type supports holder and cost(). This separates the report from pricing variations and makes an additional compatible type easier to add. Python instance methods are virtual in this sense without a virtual declaration.

Continue this lesson.

You’re previewing NovaLearn without an account. Create a free account to read the rest.

Create free account