LEARN PYTHON | Inheritance And Polymorphism

Lesson objective

Create a useful subclass, extend behaviour with super and use a common method interface without assuming inheritance is always necessary.

Learn

06.3 | INHERITANCE AND POLYMORPHISM

01 | A SPECIALISED KIND OF OBJECT

A delivery robot is a kind of robot with extra information about its destination. Inheritance lets DeliveryRobot reuse and specialise behaviour from Robot. Robot is the base class; DeliveryRobot is a subclass.

The class header class DeliveryRobot(Robot): states that relationship. An instance can use methods found on its class or inherited from its base classes. This is an is-a design, not merely a convenient way to copy code.

A library has books but is not a book. That relationship is better represented by composition, which the next lesson explores. Choosing the relationship before the syntax prevents an unnecessarily tangled hierarchy.

02 | REUSE A BASE METHOD

The base Robot initialises a name and provides describe(). DeliveryRobot initially inherits both without redefining them. A call on the subclass instance finds the inherited method and uses that instance as self.

isinstance(delivery, Robot) is true because a DeliveryRobot instance is also an instance of its base type. This does not automatically prove the subclass preserves every behavioural promise you intended.

The subclass below uses pass because its body needs a statement even when it adds nothing yet. It demonstrates inheritance, not a practical reason to create an otherwise identical type.

class Robot:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return f"Robot {self.name}"

class DeliveryRobot(Robot):
    pass

delivery = DeliveryRobot("Courier")
print(delivery.describe())
print(isinstance(delivery, Robot))

EXPECTED OUTPUT

Robot Courier
True

03 | EXTEND INITIALISATION WITH SUPER

When a subclass defines its own __init__, it must arrange any base initialisation it relies on. super().__init__(name) delegates that part through Python’s method-resolution order. In this simple single-inheritance design, it calls Robot’s initializer.

super() does not mean create another separate Robot object. It finds the next implementation for the same current instance. The base method assigns name on the delivery instance, then the subclass assigns destination.

If you omit the base initialisation and later call a method using self.name, that attribute may be missing. Inspect the complete starting state each type promises.

class Robot:
    def __init__(self, name):
        self.name = name

class DeliveryRobot(Robot):
    def __init__(self, name, destination):
        super().__init__(name)
        self.destination = destination

delivery = DeliveryRobot("Courier", "Library")
print(delivery.name, delivery.destination)

EXPECTED OUTPUT

Courier Library

04 | OVERRIDE A METHOD

Overriding means the subclass supplies a method with the same name. A call on a delivery instance then uses its describe implementation rather than simply using the base one.

The override can call super().describe() to extend the existing message. Or it can provide a replacement result when that still fits the shared contract. Here, every describe returns text and does not change the object.

Keep parameter expectations and result meaning compatible for supported use. A subclass describe requiring an extra compulsory argument would break a caller expecting describe() with no arguments.

class Robot:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return f"Robot {self.name}"

class DeliveryRobot(Robot):
    def __init__(self, name, destination):
        super().__init__(name)
        self.destination = destination

    def describe(self):
        return super().describe() + f" delivering to {self.destination}"

print(DeliveryRobot("Courier", "Library").describe())

EXPECTED OUTPUT

Robot Courier delivering to Library

05 | POLYMORPHISM USES A COMMON REQUEST

Polymorphism lets code use a common operation with objects providing different suitable implementations. A loop calling describe() does not need a separate if branch for every specialised robot.

Python resolves the method using the actual object at runtime. This is dynamic dispatch. A base type reference does not force the base implementation when the object supplies an override.

The benefit is a stable caller: adding another type with a compatible describe method can work without rewriting that reporting loop.

class DeliveryRobot:
    def describe(self):
        return "Delivering a book"

class SurveyRobot:
    def describe(self):
        return "Measuring a room"

for robot in [DeliveryRobot(), SurveyRobot()]:
    print(robot.describe())

EXPECTED OUTPUT

Delivering a book
Measuring a room

06 | DUCK TYPING DOES NOT REQUIRE A SHARED BASE

The previous two classes have no custom common base. They still work with the loop because both provide a suitable describe(). This style is often called duck typing: the required behaviour matters more than an explicit inheritance relationship.

Duck typing is not a promise that any object will work. If an object lacks describe, returns an unsuitable value or violates the caller’s assumptions, the interface is incompatible. A type hint or Protocol can later document that expectation.

Inheritance and polymorphism are connected ideas but not synonyms. Inheritance defines a type relationship; polymorphic use can also arise through compatible behaviour without that relationship.

07 | A SUBCLASS MUST REMAIN A SUITABLE SUBSTITUTE

Suppose a base lending item promises checkout() when available. A subclass that always raises an unrelated error may fit the class hierarchy syntactically while failing the behaviour the caller relies on.

Ask whether every supported caller can use the specialised object under the shared contract. Preserve meaningful inputs, result types and state rules. A new implementation should not silently require extra prerequisites that base callers were never told about.

Deep hierarchies can make these obligations harder to track. Prefer a small useful hierarchy and test shared behaviour across each type. Code reuse alone is not enough evidence for an is-a relationship.

08 | TRACE WHICH IMPLEMENTATION IS USED

Choose the actual object type. The fixed caller requests describe() with no arguments, then the model shows the implementation selected and returned text. The caller itself stays the same.

    09 | USE INHERITANCE WHEN IT EXPLAINS THE DOMAIN

    Inheritance may suit related notification types that all send a message, or specialised game entities that satisfy the same update contract. Composition may suit a robot using a replaceable sensor, because the robot has a sensor rather than being one.

    Do not make Library inherit from Book simply to access title. Give Library references to books. Do not force every record into a large base class if the shared behaviour is small or nonexistent.

    For an exercise, create two compatible describe methods and one reporting function. Then add an incompatible object deliberately in a controlled test and explain why the common interface matters.

    10 | TEST THE SHARED CONTRACT

    Test every implementation with the same expected calling pattern. Check that it returns text, requires no extra compulsory arguments and leaves state unchanged when that is part of the contract.

    For subclass initialisation, check inherited and new attributes. For an override extending base behaviour, test the combined result rather than merely the subclass’s extra words.

    The library project will mainly use composition rather than an inheritance tree. Choosing that design is part of understanding this lesson, not a failure to use the syntax you just learned.

    PUT YOUR SKILLS TO WORK

    Use the Section 6 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.

    Section 6 challenges Python course navigation

    MATCH THE PYTHON TERM TO ITS MEANING

    Terminology

    Terminology

    Inheritance

    Defining a class in relation to a base class.

    Base class

    A class whose attributes or behaviour can be inherited.

    Subclass

    A class specialising or extending a base class.

    Inherited method

    A method found through the class’s base relationship.

    Override

    A subclass implementation replacing a same-named inherited method.

    super

    A mechanism delegating to the next implementation in method resolution.

    Method resolution order

    The order Python uses to search class implementations.

    Polymorphism

    Using a shared operation with objects providing suitable different implementations.

    Dynamic dispatch

    Selecting a method implementation using the actual runtime object.

    Duck typing

    Using an object because it supports the required behaviour.

    Interface compatibility

    Meeting the calling and behavioural expectations of a shared operation.

    Substitutability

    A specialised object remaining suitable under the shared contract.

    Is-a relationship

    A useful type-specialisation relationship.

    Has-a relationship

    A relationship represented by containing or referring to another object.

    isinstance

    A runtime test recognising a type and its subclasses.

    Hierarchy

    An arrangement of base and derived types.

    Delegation

    Passing part of a job to another implementation or object.

    Contract

    The expectations a caller can rely on for an operation.

    Questions

    Questions

    CHECK YOUR UNDERSTANDING

    Select all correct choices. Each exact set earns one point.

    1. Which relationship best supports inheritance?
    2. What does super().__init__(name) do in the simple subclass example?
    3. What is overriding?
    4. What is polymorphism?
    5. Which statements about duck typing are correct?
    6. A base caller expects describe(). A subclass requires describe(extra). What is the risk?
    7. A subclass defines __init__. What must it consider?
    8. Which is a sensible hierarchy choice?

    EXPLAIN AND COMPARE

    Write your answers before revealing each sample.

    1. Explain an is-a relationship.

    2. Describe super in this single-inheritance example.

    3. Explain overriding.

    4. Explain polymorphic reporting.

    5. Compare inheritance and duck typing.

    6. Evaluate an extra required override argument.

    7. Explain missing inherited state.

    8. Design shared-contract tests.

    CODE IT | YOUR INDEPENDENT CHALLENGE

    Create compatible specialised describe methods and one reporting loop. Test the shared interface.

    Work in your Python editor. Keep a working version, explain one design choice and record at least two tests. Use the Learn tab for worked examples before comparing your result.

    Flashcards

    Flashcards

    Click to flip. Select the ideas you need to revisit.

    0 cards selected for revision.

      Selections are kept while this page is open.

      Workbook

      Workbook

      COMING SOON

      The workbook for Inheritance And Polymorphism is coming soon. Complete the matching task and written questions in your exercise book.