Inheritance: a class that builds on another
When one kind of object is a special case of another (a book is a product), a subclass inherits everything the parent has and adds or changes only what is different.
- Write
class Child(Parent):and use the parent's__init__and methods on child objects - Change a class attribute or add a method in the subclass only
- Use
isinstanceandissubclass, and catch a family of your own exceptions by their parent class
Runtime Market sells ordinary products, books and downloads. They are all products (an SKU, a name, a price), but each kind differs a little: books carry no VAT, downloads aren't shipped. Copying the Product class three times and editing each copy would mean fixing every bug three times.
class Book(Product): creates a subclass. A Book object has everything a Product has: the same __init__, attributes and methods. In the subclass body you write only what is different. When Python looks up an attribute or method on a Book, it checks the Book class first and then its parent, so a class attribute defined in Book replaces the parent's for books only.
isinstance(novel, Product) is True for a Book: a book *is a* product, and code written for products accepts it.
Run each example to see what it prints. Running examples doesn't count toward progress.
class Product: VAT_RATE = 0.2 def __init__(self, sku, name, price): self.sku = sku self.name = name self.price = price def price_with_vat(self): return round(self.price * (1 + self.VAT_RATE), 2) class Book(Product): VAT_RATE = 0.0 lamp = Product("HOM-22847", "Desk lamp", 48.0) novel = Book("BOO-10233", "The Long Road", 8.99) print(lamp.price_with_vat(), novel.price_with_vat()) print(novel.name, type(novel).__name__) print(isinstance(novel, Product), isinstance(lamp, Book))
A subclass can also add methods. They exist only on the subclass: inheritance goes from parent to child, never the other way.
class Product: def __init__(self, sku, name, price): self.sku = sku self.name = name self.price = price class DigitalProduct(Product): def download_url(self): return "https://downloads.runtime.market/" + self.sku.lower() ebook = DigitalProduct("DIG-30115", "Python Basics (e-book)", 12.0) print(ebook.download_url()) lamp = Product("HOM-22847", "Desk lamp", 48.0) print(lamp.download_url())
class Product: SHIPS = True def __init__(self, sku, name, price): self.sku = sku self.name = name self.price = price class DigitalProduct(Product): SHIPS = False def download_url(self): return "https://downloads.runtime.market/" + self.sku.lower() basket = [ Product("HOM-22847", "Desk lamp", 48.0), DigitalProduct("DIG-30115", "Python Basics (e-book)", 12.0), Product("SPO-11681", "Yoga mat", 31.0), ] to_ship = [item.sku for item in basket if item.SHIPS] print("ship:", to_ship) for item in basket: if isinstance(item, DigitalProduct): print("email link:", item.download_url()) print(issubclass(DigitalProduct, Product), issubclass(Product, DigitalProduct))
You have used inheritance since the Exceptions & Validation module: class QuantityError(Exception) made a subclass of Exception. Exception families work the same way: except catches the class you name and all its subclasses, which is why a program can define ImportProblem and catch BadRow and MissingColumn with one except ImportProblem.
When to use it: when the new class really is a special kind of the parent and should be usable anywhere the parent is. Mistakes to avoid: inheriting just to reuse a few lines of code from an unrelated class (the Inheritance & Composition module shows the alternative); calling a subclass-only method on objects that may be the parent; and long isinstance chains to decide behaviour (the next lessons replace them).
class Child(Parent):inherits the parent's__init__, methods and class attributes.- Lookup checks the object's class first, then its parents, so the subclass only states what differs.
isinstance(obj, Parent)is True for subclass objects;except Parentcatches every subclass error.- Next: replacing a parent's method in a subclass, and extending it with super().