Single Inheritance in Python

Learn how single inheritance works in Python, how to extend a parent class with additional attributes and methods, and patterns for building clean class hierarchies.

6 min read

Python single inheritance is the most common and straightforward form of class inheritance, where a child class inherits from exactly one parent class and receives all of its attributes and methods automatically. When you place a single class name in parentheses after your new class name, you create a direct parent-child relationship that gives the child everything the parent knows how to do without writing a single additional line of code. The child can then add new attributes and methods that the parent never had, override specific methods to customize their behavior, and extend the parent's constructor with super() to initialize additional data. Single inheritance avoids the complexity of multiple inheritance while providing the full benefits of code reuse, and it is the right choice for the vast majority of class hierarchies in everyday Python programming.

The article on inheritance in Python explained introduced the core concepts: the is-a relationship, the syntax for declaring a parent class, and the super() function for delegating to parent methods. This article goes deeper into single inheritance specifically, covering patterns for extending constructors, adding and overriding methods, building multi-level inheritance chains, and recognizing when single inheritance is and is not the right design choice. Every concept here builds on the foundation of classes, objects, and methods covered in the earlier articles of this section, and if you are comfortable defining a class and writing its constructor, you have all the syntax you need to start building inheritance hierarchies.

The power of single inheritance lies in its simplicity. The child class does exactly what the parent does, plus whatever you add or change, and there is only one parent to think about. There is no ambiguity about which parent's method to call, no method resolution order to puzzle over, and no risk of conflicting implementations from multiple ancestors. This simplicity makes single inheritance the default recommendation for code reuse through class hierarchies in Python.

Extending the parent constructor with super()

The most common pattern in single inheritance is a child class that needs to initialize the parent's attributes and then add its own. The child's constructor calls super().init() with the arguments the parent expects, and after that call returns, the child adds its own attribute assignments. This pattern ensures that the parent's initialization logic runs exactly once, in the correct order, without the child needing to know the internal details of how the parent stores its data.

Here is a parent class for a generic notification and a child class that extends it with additional delivery tracking:

pythonpython
class Notification:
    def __init__(self, recipient, message):
        self.recipient = recipient
        self.message = message
        self.sent = False
 
    def send(self):
        print(f"Sending to {self.recipient}: {self.message}")
        self.sent = True

The parent class stores a recipient, a message, and a sent flag, and provides a send method that prints the notification and marks it as sent. The child class inherits all of this and adds delivery tracking:

pythonpython
class TrackedNotification(Notification):
    def __init__(self, recipient, message, delivery_method):
        super().__init__(recipient, message)
        self.delivery_method = delivery_method
        self.delivery_attempts = 0

The TrackedNotification constructor accepts all the parameters the parent needs plus an additional delivery_method. It calls super().init() to let the parent set recipient, message, and sent, then adds delivery_method and delivery_attempts. The send method is inherited as-is; a TrackedNotification object can call send() and it will work correctly because the parent's method reads attributes that the parent's constructor set up. The child did not need to reimplement send or understand how Notification formats its output.

Adding new methods and overriding inherited ones

A child class can add entirely new methods that the parent does not have, extending the class's capabilities in a new direction. It can also override inherited methods by defining a method with the same name, replacing the parent's behavior with its own. Overriding is how child classes specialize: the parent provides a general implementation, and the child provides a more specific one for its particular use case.

The following child class adds a new method for tracking delivery attempts and overrides the send method to increment the attempt counter each time a notification is sent:

pythonpython
class TrackedNotification(Notification):
    def __init__(self, recipient, message, delivery_method):
        super().__init__(recipient, message)
        self.delivery_method = delivery_method
        self.delivery_attempts = 0
 
    def send(self):
        self.delivery_attempts += 1
        super().send()
 
    def retry_count(self):
        return max(0, self.delivery_attempts - 1)

The overriding send method increments the attempt counter and then calls super().send() to let the parent perform the actual sending logic. This is the extending pattern rather than the replacing pattern: the child does its extra work and then delegates to the parent for the shared work. If the parent's send method ever changes, the child automatically picks up the new behavior because it calls through super() rather than duplicating the parent's logic.

The retry_count method is entirely new. The parent Notification class has no concept of delivery attempts or retries, and no code that works with Notification objects expects a retry_count method. The child adds this capability without affecting any existing code that uses the parent class, which is the essence of extending through inheritance.

Multi-level inheritance chains

Inheritance chains can extend through multiple levels. A class can inherit from a child class, creating a grandparent-parent-child chain where each level adds or overrides behavior. The pattern is the same at each level: call super().init() to let the ancestor initialize its attributes, then add your own. Here is a three-level chain where each class builds on the one above it:

pythonpython
class Animal:
    def __init__(self, species):
        self.species = species
 
class Mammal(Animal):
    def __init__(self, species, fur_color):
        super().__init__(species)
        self.fur_color = fur_color
 
class Dog(Mammal):
    def __init__(self, fur_color, breed):
        super().__init__("Canis familiaris", fur_color)
        self.breed = breed

The Dog class calls super().init(), which resolves to Mammal's constructor, which in turn calls super().init(), which resolves to Animal's constructor. Each level initializes the attributes it is responsible for, and the full chain ensures that a Dog object has species, fur_color, and breed all set correctly. The caller only provides fur_color and breed; the species is hardcoded by Dog because every dog shares the same species.

Multi-level chains are powerful but should be used sparingly. Each additional level adds cognitive overhead because understanding a class at the bottom of the chain requires understanding every class above it. If you find yourself building chains deeper than three levels, consider whether composition or a flatter design would serve the same purpose with less complexity. The article on composition versus inheritance later in this section explores that tradeoff in detail.

When single inheritance is the right choice

Single inheritance is appropriate when the is-a relationship is clear and the parent provides a meaningful base of shared behavior that multiple children will use. A parent class that defines a common interface, stores shared data, and provides default method implementations that children can use or override is a well-designed base class. The child classes add specialization without duplicating the shared foundation.

Avoid single inheritance when the parent exists only to share a few utility methods that could be standalone functions, when the child overrides most of the parent's methods, or when the relationship between the classes is better described as has-a than is-a. A Car class that inherits from an Engine class is probably a design mistake because a car has an engine but is not itself an engine. The article on creating your first Python class covers the fundamentals of class design, and applying the is-a test to every inheritance decision keeps your hierarchies clean and maintainable.

Rune AI

Rune AI

Key Insights

  • Single inheritance means a child class inherits from exactly one parent class, receiving all its attributes and methods.
  • Use super().init() in the child's constructor to ensure the parent's initialization logic runs before the child adds its own attributes.
  • The child can add new methods and attributes that the parent does not have, extending the class's capabilities.
  • Method overriding lets the child replace a parent method with its own implementation while keeping the same method name.
  • Inheritance chains should be kept shallow; deep hierarchies become hard to understand and maintain.
RunePowered by Rune AI

Frequently Asked Questions

What is single inheritance in Python?

Single inheritance is when a class inherits from exactly one parent class. The child class receives all the attributes and methods of the parent and can add new ones or override existing ones. Single inheritance is the most common form of inheritance in Python and is sufficient for the vast majority of class hierarchy designs. It avoids the complexity of multiple inheritance while still providing code reuse through a clear parent-child relationship.

How do I call a parent class method from a child class?

Use the super() function. Inside any child class method, calling super().method_name() delegates the call to the parent class's version of that method. The most common use is super().__init__() in the child's constructor to let the parent initialize its attributes before the child adds its own. This pattern ensures that the parent's setup logic runs correctly even if the parent class changes in the future.

Can a grandchild class inherit from a child class?

Yes, inheritance chains can extend through multiple levels. A class can inherit from a child class, which itself inherits from a parent, creating a grandparent-parent-grandchild chain. The grandchild receives everything from both ancestors. However, deep inheritance chains can become hard to understand and maintain. Most Python style guides recommend keeping inheritance hierarchies shallow, typically no more than two or three levels.

Conclusion

Single inheritance is the workhorse of Python class hierarchies. It gives you code reuse through a clear parent-child relationship without the complexity of multiple inheritance. The patterns of extending constructors with super(), adding new methods, and overriding inherited behavior cover most inheritance needs in everyday Python programming. Master single inheritance before moving on to more complex inheritance patterns.