Back to Blog
Python

Python Base Class Derived Class: How Inheritance Works

python base class derived class: Learn Python base class and derived class relationships: inheritance syntax, super(), method overriding, MRO, and composition tradeoffs.

PythonInheritanceOOPMethod Resolution OrderAbstract Base ClassesComposition
Diagram of a Python derived class inheriting from a base class with shared and overridden methods

The python base class derived class relationship is declared with the syntax class Derived(Base):. This single line establishes an inheritance relationship: the derived class receives all attributes and methods from the base class, and can then extend them, override them, or leave them untouched. Understanding how this relationship behaves at runtime is essential for designing class hierarchies that are maintainable rather than fragile.

Basic Inheritance Syntax

class Base: def __init__(self, name): self.name = name def describe(self): return f"Base instance named {self.name}" class Derived(Base): def extra(self): return "behavior defined only in Derived"

When you instantiate Derived("example"), the instance has access to describe() from Base and extra() from Derived. The constructor is also inherited, so Derived does not need to define its own __init__ unless it must initialize additional state.

Attribute lookup follows a simple rule: Python checks the instance's class first, then walks up the inheritance chain. If Derived defines a method with the same name as one in Base, the derived version wins.

Method Overriding and super()

Overriding is how a derived class specializes the behavior of a base class method. The derived class defines a method with the same name, and that definition replaces the base implementation for instances of the derived class.

class Derived(Base): def __init__(self, name, extra_field): super().__init__(name) self.extra_field = extra_field def describe(self): return f"Derived instance named {self.name} with {self.extra_field}"

super() returns a proxy that delegates method calls to the next class in the method resolution order. Calling super().__init__(name) runs the base class constructor, which sets self.name. If you omit that call, self.name is never assigned, and later attribute access raises AttributeError. This is the most common failure mode when extending a base class.

The same super() mechanism works for any method, not just __init__. If a base class method performs validation, logging, or state updates, a derived class can call super().method() to preserve that behavior and then add its own logic around it.

Multiple Inheritance and the Method Resolution Order

Python allows a derived class to inherit from more than one base class:

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

When C().method() is called, Python resolves the name using the C3 linearization algorithm, which produces a consistent order across the entire hierarchy. For C, the MRO is C, A, B, object. You can inspect it directly with C.__mro__ or C.mro().

The order in which base classes are listed matters. class C(A, B) resolves method to A's implementation, while class C(B, A) resolves it to B's. If the two base classes have unrelated method sets, the order has no practical effect. If they define the same method, the first listed base class wins.

Multiple inheritance becomes genuinely useful with mixins: small base classes that each contribute a focused behavior. A derived class can combine several mixins without inheriting unrelated state or logic.

Abstract Base Classes

When a base class exists only to define an interface that derived classes must implement, use the abc module:

from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14159 * self.radius ** 2

Shape cannot be instantiated because it contains an abstract method. Circle must implement area(); if it does not, instantiating Circle raises TypeError. This gives you a compile-time-like guarantee that every concrete derived class provides the required interface.

Abstract base classes are the right tool when you have a family of classes that must all expose the same operations but implement them differently. They also work with isinstance() checks, so isinstance(circle, Shape) returns True even though Shape is abstract.

When Inheritance Fits vs Composition

Inheritance models an "is-a" relationship. Circle is a Shape, so inheritance is appropriate. But inheritance also couples the derived class to the base class's implementation details. If the base class changes its internal structure, every derived class can break.

Composition models a "has-a" relationship and is often the better choice when you only want to reuse behavior. Instead of inheriting from a Logger base class, a class can hold a Logger instance and delegate to it. This keeps the two classes independent and makes testing easier because the logger can be replaced with a mock.

A practical rule: use inheritance when the derived class genuinely specializes the base class and when the base class was designed to be extended. Use composition when you are inheriting merely to access a few methods, because that coupling will eventually become a maintenance burden.

Common Pitfalls and Runtime Behavior

Several runtime behaviors of Python inheritance regularly cause bugs. The first is forgetting to call super().__init__() in a derived class that defines its own constructor. The base class's initialization never runs, and attributes it would have set are missing.

The second is changing a method's signature during override. If code calls derived.describe() expecting the base signature, and the derived version requires an extra argument, the call raises TypeError. Python does not enforce signature compatibility between overridden methods, so the failure appears only at runtime.

The third is relying on type(obj) == Base checks. Because isinstance(obj, Base) returns True for derived instances, equality checks against the exact class will reject valid derived objects. Use isinstance() when you intend to accept any subclass.

Finally, deep inheritance chains increase the cost of attribute lookup and make debugging harder. A hierarchy of three or four levels is usually manageable; beyond that, reconsider whether composition or mixins would serve better.

python base class derived class: Practical Usage and Code Ex | RYUSLOG DEV