Inheritance
Pixel Town has houses, shops and a warehouse. They are all buildings: each has a name and a number of windows to light. With inheritance you write what they share once, in a Building class, and every other class starts from it.
1class Building:2 def __init__(self, name, windows):3 self.name = name4 self.windows = windows5 6 def describe(self):7 return f"{self.name} with {self.windows} windows"8 9class House(Building):10 pass11 12home = House("Ada's house", 4)13print(home.describe())14print(home.windows)
Ada's house with 4 windows 4
Parent and child
class House(Building): makes House a child class (or subclass) of Building, its parent class (or base class). The class in the parentheses is the one it inherits from. House has no code of its own (pass means "nothing here"), yet a house gets everything a building has: __init__, describe and the attributes they set.
When you call home.describe(), Python looks for describe in House first. It's not there, so Python looks in the parent, Building, and finds it. A child can also add things the parent doesn't have:
1class Building:2 def __init__(self, name, windows):3 self.name = name4 self.windows = windows5 6class Shop(Building):7 def open_up(self):8 print(f"{self.name} is open")9 10bakery = Shop("Bakery", 6)11bakery.open_up()12town_hall = Building("Town hall", 12)13town_hall.open_up()
Bakery is open
Traceback (most recent call last):
File "main.py", line 13, in <module>
town_hall.open_up()
AttributeError: 'Building' object has no attribute 'open_up'A shop can open_up, but a plain building can't: methods go from parent to child, never the other way.
Overriding a method
If the child defines a method with the same name as the parent, the child's version wins for objects of the child class. That is called overriding.
What does this print?
1class Building:2 def lights_needed(self):3 return 24 5class Warehouse(Building):6 def lights_needed(self):7 return 108 9print(Building().lights_needed(), Warehouse().lights_needed())
A warehouse finds lights_needed in Warehouse first and stops looking, so it gets 10. A plain building only has the parent's version, 2.
super()
Often the child wants to do what the parent does and a bit more. super() gives you the parent's version of a method, so you can call it instead of copying its code. It's most common in __init__:
1class Building:2 def __init__(self, name, windows):3 self.name = name4 self.windows = windows5 6 def describe(self):7 return f"{self.name} with {self.windows} windows"8 9class Shop(Building):10 def __init__(self, name, windows, goods):11 super().__init__(name, windows)12 self.goods = goods13 14 def describe(self):15 return super().describe() + f", selling {self.goods}"16 17bakery = Shop("Bakery", 6, "bread")18print(bakery.describe())
Bakery with 6 windows, selling bread
Shop.__init__ lets Building.__init__ set name and windows, then adds goods. Shop.describe takes the parent's sentence and adds to its end. Press Step to watch the calls go from the child up to the parent and back; self is the same shop the whole time.
If a child's __init__ forgets to call super().__init__, the parent's never runs, and its attributes are missing:
1class Building:2 def __init__(self, name):3 self.name = name4 5class House(Building):6 def __init__(self, name, owner):7 self.owner = owner8 9home = House("Rose Cottage", "Ada")10print(home.owner)11print(home.name)
Ada
Traceback (most recent call last):
File "main.py", line 11, in <module>
print(home.name)
AttributeError: 'House' object has no attribute 'name'Polymorphism
Different classes can answer the same method call in their own way. Code that only calls the method doesn't need to know which class it has. That idea is called polymorphism:
1class Building:2 def __init__(self, name):3 self.name = name4 5 def lights_needed(self):6 return 27 8class House(Building):9 pass10 11class Warehouse(Building):12 def lights_needed(self):13 return 1014 15class Mine(Building):16 def lights_needed(self):17 return 2518 19street = [House("Rose Cottage"), Warehouse("Depot"), Mine("North mine")]20for b in street:21 print(b.name, b.lights_needed())22print("Total:", sum([b.lights_needed() for b in street]))
Rose Cottage 2 Depot 10 North mine 25 Total: 37
The loop treats every item the same way. Add a new kind of building tomorrow, and the loop keeps working without a change.
isinstance and issubclass
isinstance(obj, Class) asks "is this object a Class?", and the answer counts child classes too: a warehouse is a building. issubclass(Child, Parent) asks the same about two classes.
1class Building:2 pass3 4class Warehouse(Building):5 pass6 7depot = Warehouse()8print(isinstance(depot, Warehouse), isinstance(depot, Building))9print(isinstance(Building(), Warehouse))10print(issubclass(Warehouse, Building), issubclass(Building, Warehouse))11print(type(depot) == Building)
True True False True False False
Note the last line: type() gives the exact class, so a warehouse's type is Warehouse, not Building. That's why isinstance is usually the better check. It also takes a tuple of classes: isinstance(x, (int, float)) is true for either kind of number.
What does this print?
1class Building:2 pass3 4class Shop(Building):5 pass6 7class Bakery(Shop):8 pass9 10print(isinstance(Bakery(), Building))
Inheritance goes down more than one level. A bakery is a shop, and a shop is a building, so a bakery is a building too.
Everything inherits from object
A class with no parentheses, like class Lamp:, still has a parent: Python's built-in object. Every class leads back to it, including int, str and list. So every value in Python is an object:
1class Lamp:2 pass3 4print(issubclass(Lamp, object))5print(isinstance(5, object), isinstance("ore", object))6print(issubclass(bool, int))7print(isinstance(True, int))
True True True True True
object gives every class a few basics, like a do-nothing __init__: that's why Lamp() works even though Lamp defines none. The last two lines are a surprise: bool is a child class of int, so True counts as an int (it behaves like 1).
When to use inheritance
Use inheritance when the child really is a kind of the parent: a warehouse is a building, a savings account is a bank account. If one thing just has another, store it as an attribute instead. A street is not a lamp; a street has lamps:
1class Lamp:2 def __init__(self, brightness):3 self.brightness = brightness4 5class Street:6 def __init__(self, name):7 self.name = name8 self.lamps = []9 10 def add(self, lamp):11 self.lamps.append(lamp)12 13mill = Street("Mill Road")14mill.add(Lamp(3))15mill.add(Lamp(5))16print(mill.name, len(mill.lamps), "lamps")
Mill Road 2 lamps
Keep class families small and shallow. One or two levels, like Building and its children, are easy to follow; a long chain makes you jump between many classes to see what one method does.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
A noisy mine
✓Make Mine a child class of Building and override sound so it returns "clang". The program should print the two lines below.
quiet clang
1class Building:2 def sound(self):3 return "quiet"4 5class Mine:6 pass7 8print(Building().sound())9print(Mine().sound())
A savings account
✓Savings.__init__ should let the parent set owner and balance, then store rate. Fix it with super() so the program prints the line below.
Wick 5.0
1class Account:2 def __init__(self, owner, balance):3 self.owner = owner4 self.balance = balance5 6class Savings(Account):7 def __init__(self, owner, balance, rate):8 self.rate = rate9 10s = Savings("Wick", 100, 0.05)11print(s.owner, s.balance * s.rate)
Only the carts
✓The list mixes Cart objects and other values. Print the total load of the items that are carts, using isinstance.
12
1class Cart:2 def __init__(self, load):3 self.load = load4 5items = [Cart(4), "rope", Cart(7), 9, Cart(1)]6total = 07# Your code here8print(total)
What you learned
class Child(Parent):makes a child class that gets all of the parent's methods and can add its own.- A method in the child with the same name overrides the parent's.
super().method(...)calls the parent's version; a child's__init__usually starts withsuper().__init__(...).- Polymorphism: different classes answer the same method call in their own way, so one loop handles them all.
isinstanceandissubclasscount parent classes too;type()gives only the exact class.- Every class inherits from
object. Use inheritance for "is a"; for "has a", use an attribute.