Timothy's library had grown beyond simple books. He now cataloged audiobooks, ebooks, and rare manuscripts—each with unique attributes. Audiobooks had narrators and durations. Ebooks had file formats and download links. Manuscripts had preservation conditions and historical significance.
His first attempt created separate classes with duplicated code:
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
class Audiobook:
def __init__(self, title, author, year, narrator, duration_minutes):
self.title = title # Duplicated
self.author = author # Duplicated
self.year = year # Duplicated
self.narrator = narrator
self.duration_minutes = duration_minutes
class Ebook:
def __init__(self, title, author, year, file_format, download_url):
self.title = title # Duplicated
self.author = author # Duplicated
self.year = year # Duplicated
self.file_format = file_format
self.download_url = download_url
Margaret found him writing the same initialization code for the third time. "You're repeating yourself," she observed. "Come to the Inheritance Gallery—where specialized blueprints inherit from general ones."
The Parent-Child Relationship
Margaret showed Timothy inheritance:
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
def get_summary(self):
return f'"{self.title}" by {self.author} ({self.year})'
class Audiobook(Book): # Inherits from Book
def __init__(self, title, author, year, narrator, duration_minutes):
super().__init__(title, author, year) # Call parent's __init__
self.narrator = narrator
self.duration_minutes = duration_minutes
# Create an audiobook
dune_audio = Audiobook("Dune", "Herbert", 1965, "Scott Brick", 1233)
# Audiobook has Book's attributes and methods
print(dune_audio.title) # "Dune"
print(dune_audio.narrator) # "Scott Brick"
print(dune_audio.get_summary()) # "Dune" by Herbert (1965)
"The Audiobook
class inherits from Book
," Margaret explained. "It gets all of Book's attributes and methods automatically. We only add what's unique to audiobooks."
Understanding super()
Timothy was curious about super()
:
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
class Audiobook(Book):
def __init__(self, title, author, year, narrator, duration_minutes):
# super() finds the parent class and calls its method
super().__init__(title, author, year)
# Now add audiobook-specific attributes
self.narrator = narrator
self.duration_minutes = duration_minutes
"super()
calls the parent class's method," Margaret explained. "It handles the shared initialization, then you add specialized attributes. This eliminates duplication."
Method Overriding
Timothy learned child classes could replace parent methods:
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
def get_summary(self):
return f'"{self.title}" by {self.author} ({self.year})'
class Audiobook(Book):
def __init__(self, title, author, year, narrator, duration_minutes):
super().__init__(title, author, year)
self.narrator = narrator
self.duration_minutes = duration_minutes
# Override parent's method
def get_summary(self):
hours = self.duration_minutes / 60
return f'"{self.title}" by {self.author} ({self.year}) - Narrated by {self.narrator}, {hours:.1f} hours'
dune = Book("Dune", "Herbert", 1965)
dune_audio = Audiobook("Dune", "Herbert", 1965, "Scott Brick", 1233)
print(dune.get_summary())
# "Dune" by Herbert (1965)
print(dune_audio.get_summary())
# "Dune" by Herbert (1965) - Narrated by Scott Brick, 20.6 hours
"The child class overrides the parent's method," Margaret noted. "When you call get_summary()
on an Audiobook
, Python uses the audiobook's version, not the book's."
Extending Parent Methods
Timothy discovered he could call the parent's method and then add to it:
class Audiobook(Book):
def __init__(self, title, author, year, narrator, duration_minutes):
super().__init__(title, author, year)
self.narrator = narrator
self.duration_minutes = duration_minutes
def get_summary(self):
# Call parent's method, then extend it
base_summary = super().get_summary()
hours = self.duration_minutes / 60
return f'{base_summary} - Narrated by {self.narrator}, {hours:.1f} hours'
"This is better than duplicating code," Margaret explained. "You reuse the parent's logic and add specialized behavior."
The isinstance Check
Margaret showed Timothy how to check object types:
dune = Book("Dune", "Herbert", 1965, 412)
dune_audio = Audiobook("Dune", "Herbert", 1965, "Scott Brick", 1233)
# isinstance checks the hierarchy
print(isinstance(dune, Book)) # True
print(isinstance(dune_audio, Book)) # True - audiobook IS a book
print(isinstance(dune_audio, Audiobook)) # True
print(isinstance(dune, Audiobook)) # False - book is NOT an audiobook
# type checks exact type only
print(type(dune_audio) == Book) # False - not exactly a Book
print(type(dune_audio) == Audiobook) # True - exactly an Audiobook
"An Audiobook
is a Book
," Margaret explained. "Use isinstance()
when checking types - it respects inheritance. Use type()
only when you need the exact class."
Method Resolution Order: How Python Finds Methods
Timothy wondered how Python decided which method to call when multiple classes were involved. Margaret showed him the Method Resolution Order:
class Book:
def get_info(self):
return "Book info"
class Audiobook(Book):
def get_info(self):
return "Audiobook info"
audiobook = Audiobook("Dune", "Herbert", 1965, "Scott Brick", 1233)
# Python searches classes in a specific order
print(Audiobook.__mro__)
# (<class 'Audiobook'>, <class 'Book'>, <class 'object'>)
# Order: Check Audiobook first, then Book, then object
# Every class inherits from object
"Python searches left-to-right, starting with the child class," Margaret explained. "When you call a method, Python checks Audiobook
first, then Book
, then object
. This is why child methods override parent methods."
When You Don't Need to Override init
Timothy learned he didn't always need to override initialization:
class SpecialBook(Book):
# No __init__ override - uses parent's automatically
def get_special_status(self):
return f'"{self.title}" is a special edition!'
# Uses Book's __init__ automatically
special = SpecialBook("Dune", "Herbert", 1965, 412)
print(special.get_special_status()) # Works!
"If you don't override __init__
, the child class uses the parent's," Margaret noted. "Only override when you need additional attributes."
Real-World Example: Library Catalog System
Margaret demonstrated a practical hierarchy:
class Book:
def __init__(self, title, author, year, pages):
self.title = title
self.author = author
self.year = year
self.pages = pages
self.is_checked_out = False
def checkout(self):
if self.is_checked_out:
return False
self.is_checked_out = True
return True
def return_item(self):
self.is_checked_out = False
def get_reading_time(self):
return self.pages * 2 # 2 minutes per page
class Audiobook(Book):
def __init__(self, title, author, year, narrator, duration_minutes):
super().__init__(title, author, year, pages=0) # Audiobooks have no pages
self.narrator = narrator
self.duration_minutes = duration_minutes
def get_reading_time(self):
# Override - return listening time instead
return self.duration_minutes
class Ebook(Book):
def __init__(self, title, author, year, pages, file_format, file_size_mb):
super().__init__(title, author, year, pages)
self.file_format = file_format
self.file_size_mb = file_size_mb
def get_download_info(self):
return f'{self.file_format} format, {self.file_size_mb}MB'
# All types can be checked out
catalog = [
Book("Dune", "Herbert", 1965, 412),
Audiobook("1984", "Orwell", 1949, "Simon Prebble", 720),
Ebook("Foundation", "Asimov", 1951, 255, "EPUB", 2.4)
]
# Polymorphism - treat all items the same way
for item in catalog:
print(f'{item.title}: {item.get_reading_time()} minutes')
item.checkout()
When to Use Inheritance
Margaret emphasized when inheritance made sense:
Use inheritance when:
- There's a clear "is-a" relationship (audiobook IS a book)
- Child classes share substantial behavior with the parent
- You want polymorphism (treating different types uniformly)
- The hierarchy is stable and unlikely to change
Don't use inheritance when:
- Relationship is "has-a" (car HAS an engine, not car IS an engine)
- You only need a few methods from another class
- The hierarchy becomes complex (more than 2-3 levels deep)
- Composition would be clearer
Composition vs Inheritance
Margaret showed Timothy the alternative to inheritance:
# Inheritance approach
class Audiobook(Book):
def __init__(self, title, author, year, narrator, duration_minutes):
super().__init__(title, author, year)
self.narrator = narrator
self.duration_minutes = duration_minutes
# Composition approach - "has-a" relationship
class Audiobook:
def __init__(self, title, author, year, narrator, duration_minutes):
self.book_info = Book(title, author, year) # Has a Book
self.narrator = narrator
self.duration_minutes = duration_minutes
def get_summary(self):
return f'{self.book_info.get_summary()} - Narrated by {self.narrator}'
"Composition means containing another object," Margaret explained. "Favor composition over inheritance when the relationship isn't clearly 'is-a'."
Multiple Inheritance: A Brief Look
Timothy glimpsed a more complex pattern:
class Book:
def __init__(self, title, author, year, pages=0, **kwargs):
super().__init__(**kwargs) # Pass remaining arguments up the chain
self.title = title
self.author = author
self.year = year
self.pages = pages
class AudioContent:
def __init__(self, narrator, duration_minutes, **kwargs):
super().__init__(**kwargs)
self.narrator = narrator
self.duration_minutes = duration_minutes
def get_audio_info(self):
hours = self.duration_minutes / 60
return f'Narrated by {self.narrator}, {hours:.1f} hours'
class DigitalContent:
def __init__(self, file_format, file_size_mb, **kwargs):
super().__init__(**kwargs)
self.file_format = file_format
self.file_size_mb = file_size_mb
def get_file_info(self):
return f'{self.file_format}, {self.file_size_mb}MB'
class Audiobook(Book, AudioContent, DigitalContent):
def __init__(self, title, author, year, narrator, duration_minutes, file_format, file_size_mb):
super().__init__(
title=title,
author=author,
year=year,
narrator=narrator,
duration_minutes=duration_minutes,
file_format=file_format,
file_size_mb=file_size_mb
)
# Has attributes and methods from all parent classes
audiobook = Audiobook("Dune", "Herbert", 1965, "Scott Brick", 1233, "MP3", 450)
print(audiobook.title) # From Book
print(audiobook.get_audio_info()) # From AudioContent
print(audiobook.get_file_info()) # From DigitalContent
"Multiple inheritance lets a class inherit from multiple parents," Margaret cautioned. "Notice the **kwargs
pattern - it's called cooperative inheritance. Each parent's __init__
calls super()
, passing remaining arguments up the chain. But this is complex - usually composition is clearer."
Timothy's Inheritance Wisdom
Through exploring the Inheritance Gallery, Timothy learned essential principles:
Inheritance creates "is-a" relationships: Child classes are specialized versions of parent classes.
super() calls parent methods: Eliminates code duplication in initialization and methods.
Method overriding replaces parent behavior: Child classes can provide specialized implementations.
Extending methods reuses parent logic: Call parent method with super()
, then add more.
Use isinstance() for type checking: It respects inheritance hierarchy, unlike type()
.
Method Resolution Order (MRO) determines lookup: Python searches child first, then parents, then object.
Not all methods need overriding: If parent's __init__
is sufficient, don't override it.
Polymorphism treats types uniformly: Different classes can respond to the same method calls.
Favor composition over inheritance: "Has-a" relationships often clearer than "is-a".
Keep hierarchies shallow: More than 2-3 levels becomes hard to maintain.
Multiple inheritance requires cooperation: Use **kwargs
pattern with super()
for correct behavior.
Multiple inheritance is complex: Composition is usually simpler and clearer.
Inheritance is for shared behavior: Not just for code reuse—composition can do that too.
Timothy had discovered how to build specialized blueprints from general ones—creating hierarchies where audiobooks, ebooks, and manuscripts could all be treated as books while maintaining their unique characteristics. The Inheritance Gallery had revealed the power and the limits of parent-child relationships in object-oriented design.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (0)