Concepts / Working with Multiple Levels of Inheritance

Working with Multiple Levels of Inheritance

Inheritance syntax places the parent class name in parentheses after the child class name.

  • Programming
Interactive lab

Try it: Inheritance and Method Lookup

How a subclass inherits from its parent, how Python finds an attribute by walking the instance, then the class's method resolution order up to object, how overriding and super() work, why the same call does different things on different objects (polymorphism), and what isinstance / issubclass check.

How it works

  1. class Dog(Animal) gives Dog the MRO Dog → Animal → object; Puppy(Dog) gives Puppy → Dog → Animal → object.
  2. obj.name searches obj.__dict__, then each class of type(obj).__mro__ in order; the first class that defines the name wins, so a subclass method overrides the parent's.
  3. super().method() starts the search in the class after the one the current method was defined in, e.g. to run the parent's __init__.
  4. Calling the same method on a list of different objects runs each object's own version (polymorphism).
  5. isinstance(obj, C) is True when C is in type(obj).__mro__; if no class has the name, Python raises AttributeError.

Default run (63 steps): Ready: Animal, Dog, Cat and Puppy, then the statements you picked. Each attribute is searched along the class's MRO. … Finished. Printed: Rex the dog says Woof / Tom the cat says Meow / Bo the dog says Woof! / True / Puppy / Dog / Animal / object.

Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Single inheritance only (the lab's MRO code is Python's C3 algorithm, but no class here has two parents).

Educational simulation

Loading the simulation…

One Object, Several Sources of Behavior

A child class does not start from nothing. It extends a parent class, automatically receiving the parent's variables and methods while also gaining the ability to define additional features. This means one child-class instance can combine inherited behavior with behavior introduced specifically by the child.

Key point

The relationship is one-way: the child inherits from the parent, but the parent does not inherit from the child.

Following the Class Hierarchy

Inheritance syntax places the parent class name in parentheses after the child class name. The form class ChildClass(ParentClass): tells Python that ChildClass extends ParentClass. The parent sits above the child in the hierarchy. The child receives the parent's variables and methods automatically, so an instance of the child can use inherited features as well as features defined directly in the child.

PartyAnimalname, party()CricketFanpoints, six()jname, party(), points,six()inherits fromcreates
What does a child inherit from its parent, and what does it add for itself?

In the source example, CricketFan is the child class and PartyAnimal is the parent class. PartyAnimal provides name and party(). CricketFan adds points and six(). An instance named j therefore has access to all four features: the inherited name and party(), plus the child-defined points and six().

Initializing Parent and Child State

A child class often needs to initialize two groups of variables: the variables required by the parent and the new variables required by the child. The child class can use super() inside its __init__ method to call the parent class's __init__ method. This lets the parent complete its own setup before the child adds child-specific variables.

Create CricketFanchild objectCricketFan.__init__starts child setupPartyAnimal.__init__initializes namepointschild attributecallssuper()returns, then sets
When a child object is created, how does control move through the child and parent initializers?
Code walkthrough
class PartyAnimal:
    def __init__(self, name):
        self.name = name

class CricketFan(PartyAnimal):
    def __init__(self, name, points):
        super().__init__(name)
        self.points = points

    def six(self):
        pass
  1. 1
    The parent initializer receives name and stores the inherited name variable.
  2. 2
    The child initializer calls super().__init__(name) so the parent setup is performed.
  3. 3
    After the parent initializer returns, the child stores its own points variable.
  4. 4
    The child also defines six(), which is not supplied by the parent.
  • PartyAnimal defines the parent initialization.
  • CricketFan extends PartyAnimal.
  • super().__init__(name) calls the parent initializer.
  • points is added after the parent setup.
  • six() is a method defined by CricketFan.

Keeping Inherited Behavior and Adding More

Before
class PartyAnimal:
    def party(self):
        pass

The parent provides party().

After
class PartyAnimal:
    def party(self):
        pass

class CricketFan(PartyAnimal):
    def six(self):
        pass

The child still has inherited party() and now also has six().

Extending a class does not remove the parent's functionality. CricketFan keeps access to the inherited party() method and adds its own six() method. The same combination applies to variables: name comes from the parent setup, while points is introduced by the child.

FeatureWhere it is definedAvailable on CricketFan instance
namePartyAnimalYes, inherited
party()PartyAnimalYes, inherited
pointsCricketFanYes, child-defined
six()CricketFanYes, child-defined

The source example separates inherited features from features added by CricketFan.

Worked example

Tracing the CricketFan instance

Determine which features are available on an instance j of CricketFan.

  1. 1
    Identify the parent

    CricketFan extends PartyAnimal, so PartyAnimal is the source of inherited features.

  2. 2
    Collect inherited features

    The instance receives name and party() from PartyAnimal.

  3. 3
    Collect child features

    CricketFan adds points and six().

  4. 4
    Combine the results

    The child instance can access both groups of features.

Result

j has name and party() from PartyAnimal, plus points and six() from CricketFan.

Mistakes in Parent Initialization

  • Common mistake

    Treating the child as if it receives only its own variables and methods.

    What it looks like

    Assuming a CricketFan instance has points and six() but not name and party().

    Why it fails

    A child instance contains inherited variables and methods as well as child-defined features.

    Do this instead

    Check both the parent and child class when listing what the instance can access.

  • Common mistake

    Defining the child without naming its parent in parentheses.

    What it looks like

    Writing a child class without the relationship represented by class ChildClass(ParentClass):.

    Why it fails

    The parenthesized class name tells Python which class the new class extends.

    Do this instead

    Place the parent class name in parentheses after the child class name.

  • Common mistake

    Setting child variables before calling the parent initializer.

    What it looks like
    Assigning the child-specific state before super().__init__().
    Why it fails

    The source guidance requires the parent to be fully initialized first.

    Do this instead
    Call super().__init__() before setting up child-specific variables.
  • Common mistake

    Expecting the parent to inherit child features.

    What it looks like

    Assuming PartyAnimal can use the points variable or six() defined by CricketFan.

    Why it fails

    Inheritance moves from parent to child, not from child to parent.

    Do this instead

    Treat the relationship as one-way: the child receives parent features.

Practice the Inheritance Trace

EASY

Suppose Player is a parent class with a name variable and a play() method. Suppose CricketPlayer extends Player and adds a points variable and a score() method. List the features an instance of CricketPlayer should contain, and state which class provides each feature.

Hints
  • Separate features defined by the parent from features added by the child.
  • Remember that the child instance combines inherited and child-defined features.
  • Use the PartyAnimal and CricketFan example as a model.
MEDIUM

Explain why a child initializer should call super().__init__() before assigning its own variables. In your explanation, identify what setup belongs to the parent and what setup belongs to the child.

Hints
  • The parent initializer prepares the parent class's variables.
  • The child initializer can add new variables after the parent setup is complete.

Key Takeaways

  1. 1
    Use class ChildClass(ParentClass): to define a child that extends a parent.
  2. 2
    A child inherits the parent's variables and methods automatically.
  3. 3
    Use super().__init__() inside the child initializer to run the parent initializer.
  4. 4
    Call the parent initializer before setting child-specific variables.
  5. 5
    A child instance combines inherited features with new variables and methods defined by the child.

At a glance

  • Inheritance creates a one-way parent-to-child relationship.
  • The child class receives the parent's variables and methods and can add its own.
  • The super() function lets the child call the parent class's __init__ method.
  • Parent initialization should happen before child-specific setup.
  • An instance of the child class combines inherited and newly defined functionality.

Related concepts

More concepts starting with W