Back to Blog
Python

Python Parent Class Child Class: Inheritance in Practice

python parent class child class: Learn Python inheritance with parent and child classes: constructor chaining, super(), method overriding, MRO, and maintainability tra...

inheritancesupermethod-overridingmultiple-inheritancemrooop
Diagram showing a child class inheriting from a parent class in Python, with an arrow indicating the inheritance relationship.

When you define a python parent class child class relationship, the child class inherits attributes and methods from the parent. The syntax is minimal:

class Parent: def greet(self): return "Hello from parent" class Child(Parent): pass

The Child class can call greet() even though it never defines it. This is the foundation of inheritance in Python, and most of the behavior you need to understand follows from how Python resolves attribute access and how constructors interact.

The Basic Syntax for a Parent and Child Class

A parent class is an ordinary class. A child class declares its parent in parentheses after the class name:

class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedError class Dog(Animal): def speak(self): return f"{self.name} says woof"

The Animal class defines the shared contract: every animal has a name and a speak() method. The Dog class inherits the constructor and overrides speak().

You can also add new methods and attributes to the child class without touching the parent:

class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed def fetch(self): return f"{self.name} fetched the ball"

Here Dog extends the parent's constructor with a breed attribute and adds a fetch() method that Animal never had.

The Constructor Chain: Why super() Matters

When a child class defines its own __init__, Python does not automatically call the parent's __init__. If you skip the call, the parent's initialization logic never runs.

class Animal: def __init__(self, name): self.name = name class Dog(Animal): def __init__(self, name, breed): self.breed = breed d = Dog("Rex", "Labrador") print(d.name) # AttributeError

The instance d has no name attribute because Animal.__init__ was never executed. The fix is to call super().__init__(name) inside Dog.__init__:

class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed

super() returns a proxy object that delegates method calls to the next class in the MRO. In a single-inheritance chain, that is the parent class. In a multiple-inheritance chain, it is the next class in the linearization order.

Method Overriding and super()

Overriding a method means defining a method in the child class with the same name as one in the parent. The child's version replaces the parent's for instances of the child class.

class Parent: def describe(self): return "Parent" class Child(Parent): def describe(self): return "Child: " + super().describe()

The super().describe() call gives you access to the parent's implementation. This pattern is useful when the child needs to extend the parent's behavior rather than replace it entirely. For example, a base class might validate input, and a child class adds its own validation before calling the parent's.

Attribute Lookup and the MRO

When you access an attribute on an instance, Python searches the class hierarchy in a specific order. For a single-inheritance chain, the order is: the instance's class, then its parent, then the grandparent, and so on up to object.

You can inspect this order directly:

print(Dog.__mro__) # (<class 'Dog'>, <class 'Animal'>, <class 'object'>)

The MRO determines which method wins when multiple classes define the same name. The first class in the MRO that defines the attribute is the one that gets used.

Multiple Inheritance and Method Resolution

Python allows a class to inherit from more than one parent:

class A: def method(self): return "A" class B(A): def method(self): return "B" class C(A): def method(self): return "C" class D(B, C): pass

D().method() returns "B" because B appears before C in D.__mro__. The C3 linearization algorithm determines this order, and it guarantees that a class always appears before its parents.

Multiple inheritance is powerful but easy to misuse. If two parent classes define conflicting methods, the resolution order decides the outcome, and that order is not always obvious from reading the class declaration alone. Check D.__mro__ when you need to know exactly which implementation will run.

isinstance, issubclass, and Type Checks

isinstance() and issubclass() are the standard ways to check inheritance relationships at runtime.

d = Dog("Rex", "Labrador") isinstance(d, Animal) # True isinstance(d, Dog) # True issubclass(Dog, Animal) # True issubclass(Animal, Dog) # False

isinstance() returns True if the object is an instance of the given class or any of its parent classes. issubclass() checks whether one class is a descendant of another. These checks are useful when you need to handle objects polymorphically, but overusing them can be a sign that the class design is too rigid.

When Inheritance Hurts Maintainability

Deep inheritance hierarchies are difficult to maintain because a change in a parent class can silently change the behavior of every child class. A method that works correctly for one child might break another child when the parent's implementation changes.

Prefer composition over inheritance when the relationship is not a genuine "is-a" relationship. If a class merely needs a piece of behavior from another class, delegating to a contained instance is often easier to reason about:

class Logger: def log(self, message): print(message) class Service: def __init__(self, logger): self.logger = logger def run(self): self.logger.log("Service started")

Here Service does not inherit from Logger; it holds a Logger instance. This makes the dependency explicit and easier to test.

Common Inheritance Mistakes

The most common mistake is forgetting to call super().__init__() in the child constructor, which leaves parent attributes uninitialized. Another is overriding a method without calling the parent's version when the parent's behavior is still needed.

A subtler mistake is assuming that super() always refers to the direct parent. In a multiple-inheritance hierarchy, super() refers to the next class in the MRO, which may not be the class you wrote as the parent in the class declaration. Always check __mro__ when the hierarchy has more than one level.

python parent class child class: Practical Usage and Code Ex | RYUSLOG DEV