The idea
Inheritance models an "is-a" relationship. A puppy is a dog, a car is a vehicle, a manager is an employee. When you write that relationship in code, the child class (also called a subclass) automatically receives the data and behavior of the parent class (the superclass). You write the shared parts once, and each child adds only what is different.
Without it, a Puppy class would need its own copy of every Dog method, and a fix to one copy would have to be repeated in the other. With inheritance there is one copy, and the fix reaches every subclass at once.
The syntax
Name the parent in parentheses after the child's name:
class Puppy(Dog):
def whine(self):
print("whimper")
That is the whole mechanism. Puppy now has everything Dog has, plus whine.
What is inherited
- Methods. A
Puppycan callbark()even though onlyDogdefines it. - Attributes. They are set by the parent's
__init__, so aPuppyobject has anameas soon as that constructor runs. - The type relationship. A
Puppyobject counts as aDog, so code written for dogs also accepts puppies.
print(isinstance(buddy, Dog))
print(issubclass(Puppy, Dog))
print(Puppy.__mro__)
True
True
(<class '__main__.Puppy'>, <class '__main__.Dog'>, <class 'object'>)
The last line is the method resolution order. When you call a method, Python looks in Puppy first, then Dog, then object, the base of every class, and uses the first match. That search order is how inheritance works at run time.
The constructor
A subclass that defines no __init__ of its own uses its parent's. So Puppy("Buddy") runs Dog.__init__ and sets the name. This is what the lesson relies on.
If a subclass needs extra data, it writes its own __init__ and passes the shared part up with super():
class Puppy(Dog):
def __init__(self, name, toy):
super().__init__(name)
self.toy = toy
Skipping the super().__init__(name) call is a classic bug. The parent's setup never runs, so self.name is never created, and the first call to bark() raises AttributeError: 'Puppy' object has no attribute 'name'.
Chains of inheritance
A subclass can itself be a parent. If a class inherits from Puppy, it receives whine from Puppy and bark and the constructor from Dog. Python walks up the chain until it finds the name, so an object of the bottom class is an instance of every class above it, and isinstance(x, Dog) is still True. Keep such chains short, because each level makes it harder to see where a method comes from.
The lesson's exercise
The starter code defines Dog with an __init__ that stores name and a bark method. At the bottom it creates buddy = Puppy("Buddy"), calls buddy.bark() and then buddy.whine(). You write the Puppy class between them.
- Declare the subclass. Write
class Puppy(Dog):belowDog. Do not add an__init__: the one fromDogis exactly whatPuppy("Buddy")needs. - Add the new method. Inside
Puppy, writedef whine(self):and print"whimper". Only the new behavior needs code, becausebarkis inherited.
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print("Woof! I am " + self.name)
class Puppy(Dog):
def whine(self):
print("whimper")
buddy = Puppy("Buddy")
buddy.bark()
buddy.whine()
Woof! I am Buddy
whimper
Inheritance goes one way
The child gets the parent's members, never the reverse. A plain Dog has no whine, and Dog("Rex").whine() raises AttributeError: 'Dog' object has no attribute 'whine'. Methods added in a subclass belong only to that subclass and its own children.
Common mistakes
- Writing the parent in the wrong place.
class Puppy: Dogis a syntax error. The parent goes inside parentheses before the colon. - Defining
PuppybeforeDog. Python reads top to bottom, so the parent class must already exist when the subclass line runs; otherwise you get aNameError. - Using inheritance when the relationship is not "is-a". A dog has a name, but a dog is not a name. Use inheritance for kinds of things, and plain attributes for parts.
Key takeaways
- A subclass is written
class Child(Parent):and inherits the parent's methods and attributes. - Without its own
__init__, a subclass uses the parent's. - With its own
__init__, callsuper().__init__(...)so the parent can set up its share. - Inheritance flows from parent to child only.