Back to Blog
Python

Python Single Inheritance: Syntax and Behavior

python single inheritance: Learn how Python single inheritance works: class syntax, attribute lookup, super() delegation, method overriding, and when composition is a...

inheritancesuper()method resolution orderOOPclass design
Diagram showing a child class extending a single parent class, with method lookup flowing upward through the inheritance chain.

Python single inheritance means a class can extend exactly one parent class. The child class inherits the parent's attributes and methods, and it can override or extend them without changing the parent's behavior. This is the default inheritance model in Python, and it keeps the method resolution order simple: a linear chain from the child up to the base object class.

Declaring a Child Class in Python

The syntax is minimal. Put the parent class name in parentheses after the child class name:

class Base: def __init__(self, name): self.name = name def describe(self): return f"Base named {self.name}" class Child(Base): pass

Child now has __init__ and describe available, even though it defines no methods of its own. Instances of Child are also instances of Base:

c = Child("demo") print(isinstance(c, Child)) # True print(isinstance(c, Base)) # True print(issubclass(Child, Base)) # True

The pass body is valid but unusual in real code. A child class normally adds attributes or overrides behavior.

How Attribute and Method Lookup Works

When you access an attribute on an instance, Python searches in this order:

  1. The instance's own __dict__
  2. The class's __dict__
  3. Each parent class's __dict__, walking up the inheritance chain
  4. The built-in object class

This means a child class automatically sees everything the parent defines, unless the child shadows it with a same-named attribute. The lookup is dynamic: if the parent gains a method at runtime, existing child instances see it immediately.

Using super() to Delegate to the Parent

super() returns a proxy that forwards method calls to the next class in the method resolution order. In single inheritance, that is always the parent class.

class Base: def __init__(self, name): self.name = name class Child(Base): def __init__(self, name, level): super().__init__(name) self.level = level

Calling super().__init__(name) runs the parent's constructor so the parent-owned state is initialized. Skipping it leaves self.name undefined, which often surfaces later as an AttributeError when the parent's methods run.

super() works in any method, not just __init__. A common pattern is extending a parent method while keeping its original behavior:

class Base: def describe(self): return f"Base named {self.name}" class Child(Base): def describe(self): return super().describe() + f", level {self.level}"

The child reuses the parent's logic and appends its own detail. This keeps the parent's behavior in one place instead of duplicating it.

Overriding Methods Without Breaking Parent Behavior

Overriding means defining a method with the same name as one in the parent. The child's version wins during lookup. The decision is whether to call super() inside the override.

If the override fully replaces the parent's behavior, no super() call is needed:

class Child(Base): def describe(self): return f"Child named {self.name}, level {self.level}"

If the override extends the parent's behavior, call super() and combine the results. The important constraint is that the parent method still runs with the same instance, so it sees any state the child has already set up. Call super() after the child has initialized the attributes the parent method reads.

Chained Single Inheritance and the MRO

Single inheritance does not limit you to one level. A class can inherit from a child of another class, producing a chain:

class A: pass class B(A): pass class C(B): pass

The method resolution order for C is C → B → A → object. Python exposes it through C.__mro__. Because the chain is linear, there is no ambiguity about which class provides a given method, and the diamond problem that plagues multiple inheritance cannot occur.

super() in a chained chain still works correctly. In C, super() resolves to B; in B, super() resolves to A. Each class only needs to know its immediate parent.

Common Mistakes With Single Inheritance

The most frequent error is forgetting to call super().__init__() in the child constructor. The child then inherits methods that depend on parent state, but that state was never created. The failure appears later, often as an AttributeError inside a method that assumes the attribute exists.

Another mistake is calling the parent method directly by class name:

class Child(Base): def __init__(self, name, level): Base.__init__(self, name) # works, but fragile self.level = level

This works in single inheritance, but it hardcodes the parent class name. If the hierarchy changes, the call breaks. super() resolves the parent dynamically and survives refactoring.

A third mistake is overriding a method and forgetting that the parent's version also runs. If the parent method has side effects, replacing it entirely can skip important setup. Decide explicitly whether the override is a replacement or an extension, and document that choice.

When Single Inheritance Becomes a Maintainability Problem

Deep chains are the main maintainability risk. A chain of five or six levels makes it hard to trace where a method is actually defined and which super() call initializes which attribute. The class that owns a given piece of state becomes unclear.

A practical limit is two or three levels. Beyond that, composition usually serves better: instead of inheriting behavior from a distant ancestor, hold an instance of the collaborator and delegate to it.

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

This keeps the dependency explicit and testable. The rule of thumb: use single inheritance when the child genuinely is a specialized version of the parent, and prefer composition when the relationship is "has a" rather than "is a".

python single inheritance: Practical Usage and Code Examples | RYUSLOG DEV