Python Inheritance and super(): Overriding and isinstance
A Python subclass inherits its base's methods and overrides or extends them. Why a subclass initialiser calls super, how lookup works, and isinstance vs type.
- Course: Python study plan
- Module: Inheritance, protocols and duck typing
- Kind: Lesson
- Reading time: 14 min
- Runtime: CPython 3.11
How does inheritance work in Python?
In Python, class Dog(Animal) makes Dog a subclass that inherits every method and class attribute of Animal; it overrides a method by redefining it and extends one by calling super(). Attribute lookup searches the instance, then the class, then its bases up to object, and the first match wins. Every method is virtual, so base-class code that calls self.speak() runs the override.
Lesson
Inheritance lets a class start from another: class Dog(Animal) gives Dog every attribute and method of Animal and lets it add or replace what differs. Python's version is simple in mechanism — attribute lookup walks from the instance to its class to the base classes — and the only real subtlety is initialisation: a subclass's __init__ replaces the base's, so it must call super().__init__() explicitly or the base's attributes never exist. This lesson covers the syntax, what is inherited, overriding and extending, super(), isinstance/issubclass, the lookup order, and the object root that everything descends from.
The statement
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
def describe(self):
return f"{self.name} says {self.speak()}"
class Dog(Animal):
def speak(self): # override
return "woof"
class Puppy(Dog):
def __init__(self, name, age_weeks):
super().__init__(name) # extend: run the base initialiser, then add
self.age_weeks = age_weeks
def describe(self): # extend a method
return super().describe() + f" (aged {self.age_weeks} weeks)"
p = Puppy("rex", 8)
p.describe() # 'rex says woof (aged 8 weeks)'
class Dog(Animal): names the base in parentheses. Dog inherits __init__, describe and name; it overrides speak. describe in the base calls self.speak(), and self is a Dog, so the override runs — that is polymorphism, and it needs nothing declared: every method is virtual.
What is inherited
Everything in the class namespace: methods, class attributes, properties, dunders, class methods and static methods. Instance attributes are not "inherited" in the same sense — they are created by whichever __init__ runs, which is why a subclass that defines its own __init__ must call the base's, or self.name is never set and the first describe() fails with AttributeError.
super()
super() returns a proxy that looks up attributes starting after the current class in the method resolution order (next lesson but one). super().__init__(name) calls Animal.__init__ with self bound automatically; super().describe() calls the base version of a method you are extending. Two rules. Call super().__init__() first in a subclass initialiser unless there is a reason not to, so the base's attributes exist before your code uses them. And pass the base what it expects — its parameters, not yours: Puppy.__init__ takes (name, age_weeks) and forwards only name.
The old spelling Animal.__init__(self, name) works but hard-codes the base; super() follows the MRO and survives refactoring and multiple inheritance.
Attribute lookup
p.describe is resolved by searching, in order: p.__dict__ (instance), Puppy.__dict__, Dog.__dict__, Animal.__dict__, object.__dict__. The first hit wins. That order — the class's __mro__ — is what makes overriding work (the subclass is searched first) and what makes an instance attribute shadow a class attribute of the same name. type(p) is Puppy; Puppy.__bases__ is (Dog,); Puppy.__mro__ is (Puppy, Dog, Animal, object).
isinstance and issubclass
isinstance(p, Puppy) # True
isinstance(p, Animal) # True — an instance of a subclass is an instance of the base
isinstance(p, (Dog, int)) # True — any of a tuple
issubclass(Puppy, Animal) # True
type(p) is Animal # False — type() is exact; isinstance respects inheritance
Use isinstance, not type(x) == C, when subclasses should count — which is almost always. isinstance(True, int) is True because bool subclasses int; isinstance(3, float) is False. Long isinstance chains that switch behaviour on the type are the thing polymorphism replaces (next lesson).
The object root
Every class inherits from object, explicitly or not: class A: and class A(object): are the same in Python 3. object supplies the default __init__ (no arguments), __repr__, __eq__ (identity), __hash__, __str__ (falls back to __repr__) and the machinery of attribute access. That is why a class with no methods at all can be instantiated, printed and compared.
Overriding dunders and the repr
Overriding __repr__ in a subclass usually means including the subclass's own fields; type(self).__name__ makes a base-class repr correct for every subclass automatically:
class Animal:
def __repr__(self):
return f"{type(self).__name__}({self.name!r})"
Puppy("rex", 8) then prints as Puppy('rex') without Puppy defining anything. The same trick works in __eq__ (isinstance(other, type(self))) and in class-method constructors (cls(...)).
Extending versus replacing
An override that does not call super() replaces the base behaviour; one that does extends it, before or after its own work. Both are legitimate, and the choice should be deliberate: Cat.speak replaces because the base answer is a placeholder, while Puppy.describe extends because the base text is still wanted. The trap is the accidental replacement — overriding a method that the base class relied on for bookkeeping (a close() that flushed, a __setattr__ that validated) and dropping the call, so the base's invariant quietly stops being maintained.
Inheriting from built-ins
class Stack(list) is tempting and usually wrong: list has forty methods that bypass your rules (insert, slicing assignment, extend), and its C implementation does not call your overridden __setitem__ from append. Prefer composition — hold a list in an attribute and expose the operations you mean — or subclass collections.UserList/UserDict, which are written in Python and route everything through the basic methods (lesson 6).
Pitfalls
- A subclass
__init__that forgetssuper().__init__(), leaving base attributes unset. - Calling
super().__init__()with the subclass's extra arguments. type(x) == Cwhereisinstancewas meant.- Overriding a method with a different signature, so code written for the base breaks on the subclass.
- Deep hierarchies for code reuse; three levels is a lot.
- Subclassing
dict/listto add rules.
Key takeaways
class Sub(Base)inherits the base's namespace; a subclass overrides by redefining and extends by callingsuper().- A subclass
__init__must callsuper().__init__(...)with the base's arguments, first. - Lookup walks instance → class → bases →
object; the first match wins, so overrides and shadowing follow. isinstancerespects inheritance;type()is exact;boolis anint.- Everything descends from
object; usetype(self).__name__in shared dunders; compose rather than subclass built-ins.
Common questions
What does super().__init__() do in Python?
It calls the base class's initialiser with self bound automatically, so the attributes the base sets actually exist. A subclass that defines its own __init__ replaces the base's, so it must call super().__init__(...), usually first, passing the arguments the base expects.
What happens if a subclass does not call super().__init__()?
The base class's __init__ never runs, so the attributes it would have set are missing, and the first method that uses one fails with AttributeError. Inheritance shares methods, not instance attributes — those exist only once an initialiser assigns them.
What is the difference between isinstance and type in Python?
isinstance(x, C) is true for instances of C and of any subclass, while type(x) is C matches the exact class only. Use isinstance when subclasses should count, which is almost always; isinstance(True, int) is True because bool subclasses int.
Do all Python classes inherit from object?
Yes. In Python 3, class A: and class A(object): are the same, and object supplies the default __init__, __repr__, identity-based __eq__, __hash__ and __str__. That is why a class with no methods can still be created, printed and compared.
How do I override a method in Python?
Define a method with the same name in the subclass. Without a super() call it replaces the base version; calling super().method() inside it extends the base behaviour instead. Keep the same signature, or code written for the base class breaks on the subclass.
Exercises
Animals
Build Animal(name) with speak() returning ..., describe() returning <name> says <speak()>, and a __repr__ using type(self).__name__ so every subclass prints correctly. Dog and Cat override speak (woof, meow); Puppy(Dog) takes an extra age_weeks, calls super().__init__, and extends describe with (aged <n> weeks). Read lines dog name, cat name, puppy name weeks and print each animal's repr and description.
Input: lines. Output: two lines per animal: the repr, then the description.
dog rex
puppy bit 8
prints
Dog('rex')
rex says woof
Puppy('bit')
bit says woof (aged 8 weeks)Employees and managers
Employee(name, salary) has pay() returning the salary. Manager(Employee) adds a bonus and its pay() extends the base version with super(). Read staff lines employee name salary or manager name salary bonus; print each person's pay, then the total payroll, then how many objects are instances of Employee and how many of Manager (using isinstance).
Input: lines. Output: <name> <pay> per line, then total <n>, employees <n>, managers <n>.
employee ada 100
manager bob 200 50
prints
ada 100
bob 250
total 350
employees 2
managers 1In this module: Inheritance, protocols and duck typing
- Inheritance basics — subclasses, super() and attribute lookup (this lesson)
- Polymorphism and duck typing — EAFP, hasattr and programming to behaviour
- Abstract base classes — abc and collections.abc
- Multiple inheritance and the MRO — mixins and cooperative super()
- Protocols and structural typing — typing.Protocol
- Composition over inheritance — Liskov, delegation and wrapping built-ins
- Checkpoint — Inheritance, protocols and duck typing
← Checkpoint — Classes and objects · Polymorphism and duck typing — EAFP, hasattr and programming to behaviour →