Python Inheritance

"In Python, inheritance isn't just about sharing code; it's about modeling 'is-a' relationships, creating a powerful, extensible hierarchy where specialized classes can gracefully build upon the foundations of their ancestors."- Gemini 2025

Learning Objectives

By the end of this lecture, you will be able to:

  • Understand and implement inheritance in Python classes
  • Distinguish between "is-a" and "has-a" relationships
  • Apply composition to create complex objects
  • Create and use objects effectively with classes
  • Design class hierarchies using real-world examples

Foundation - Classes and Objects Review

What is a Class?

A class is a blueprint or template for creating objects. It defines attributes (data) and methods (functions) that objects of that type will have.

What is an Object?

An object is an instance of a class - a specific realization of the blueprint with actual values.

# Basic class structure
class Movie:
    def __init__(self, title, year):
        self.title = title
        self.year = year

    def display_info(self):
        return f"{self.title} ({self.year})"

# Creating objects
movie1 = Movie("The Matrix", 1999)
movie2 = Movie("Inception", 2010)

Understanding Relationships

The "Is-A" Relationship (Inheritance)

When one class is a specialized version of another class, we use inheritance.

Examples:
  • A Dog is a Animal
  • A ActionMovie is a Movie
  • A Student is a Person
Key Characteristics:
  • Child class inherits all attributes and methods from parent
  • Child class can override parent methods
  • Child class can add new attributes and methods
  • Uses class Child(Parent): syntax

The "Has-A" Relationship (Composition)

When one class has a component that is another class, we use composition.

Examples:
  • A Movie has a Director
  • A Car has an Engine
  • A Library has Books
Key Characteristics:
  • One object contains references to other objects
  • Objects can exist independently
  • More flexible than inheritance
  • Uses object attributes to store other objects

Inheritance in Detail

Basic Inheritance Syntax

class Movie:  # Parent/Base class
    def __init__(self, title, year, genre):
        self.title = title
        self.year = year
        self.genre = genre

    def play(self):
        return f"Playing {self.title}"

class ActionMovie(Movie):  # Child/Derived class
    def __init__(self, title, year, lead_actor, budget):
        super().__init__(title, year, "Action")  # Call parent constructor
        self.lead_actor = lead_actor
        self.budget = budget

    def explosive_scene(self):
        return f"{self.lead_actor} saves the day!"

Method Overriding

Child classes can provide their own implementation of parent methods:

class DocumentaryMovie(Movie):
def __init__(self, title, year, subject):
    super().__init__(title, year, "Documentary")
    self.subject = subject

def play(self):  # Override parent method
    return f"Now presenting the documentary: {self.title} about {self.subject}"

Benefits of Inheritance

  • Code Reusability: Don't repeat common functionality
  • Polymorphism: Treat different objects the same way
  • Extensibility: Easy to add new specialized classes
  • Maintenance: Changes to parent affect all children

Composition in Detail

Basic Composition

Instead of inheritance, we can build complex objects by combining simpler ones:

class Director:
    def __init__(self, name, birth_year):
        self.name = name
        self.birth_year = birth_year

    def __str__(self):
        return f"Director: {self.name}"

class Actor:
    def __init__(self, name, birth_year):
        self.name = name
        self.birth_year = birth_year

    def __str__(self):
        return f"Actor: {self.name}"

class Movie:
    def __init__(self, title, year, director):
        self.title = title
        self.year = year
        self.director = director  # Movie HAS-A Director
        self.cast = []           # Movie HAS-A list of Actors

    def add_actor(self, actor):
        self.cast.append(actor)

    def get_credits(self):
        credits = f"{self.title} ({self.year})\n"
        credits += f"Directed by: {self.director.name}\n"
        credits += "Starring: " + ", ".join([actor.name for actor in self.cast])
        return credits
Use Inheritance when:
  • There's a clear "is-a" relationship
  • You want to share common behavior
  • You need polymorphism
Use Composition when:
  • There's a "has-a" relationship
  • You need more flexibility
  • Objects have different lifecycles
  • You want to avoid deep inheritance hierarchies

Practical Example and Best Practices

Combining Both Concepts

You can use inheritance and composition together:

class StreamingMovie(Movie):  # Inheritance: StreamingMovie IS-A Movie
    def __init__(self, title, year, director, platform, subscription_required):
        super().__init__(title, year, director)  # Call parent constructor
        self.platform = platform
        self.subscription_required = subscription_required

    def stream(self):
        if self.subscription_required:
            return f"Subscribe to {self.platform} to watch {self.title}"
        else:
            return f"Now streaming {self.title} on {self.platform}"

Best Practices

  1. Favor composition over inheritance when possible
  2. Keep inheritance hierarchies shallow (3-4 levels max)
  3. Use meaningful class names that clearly indicate relationships
  4. Override __str__ and __repr__ for better object representation
  5. Use super() to call parent methods properly
  6. Think about the real-world relationship before choosing inheritance vs composition

Summary and Key Takeaways

Remember the Relationships

  • Inheritance (is-a): ActionMovie is a Movie
  • Composition (has-a): Movie has a Director

When to Use Each

  • Inheritance: When you need to specialize behavior and share common functionality
  • Composition: When you need to build complex objects from simpler parts

Design Principles

  • Start with composition, use inheritance when it makes clear sense
  • Think about how objects relate in the real world
  • Keep your class hierarchies simple and understandable
  • Always consider maintainability and flexibility

Next Steps

  • Practice creating class hierarchies with different domains
  • Experiment with multiple inheritance (advanced topic)
  • Learn about abstract base classes and interfaces
  • Explore design patterns that use these concepts
# Composition classes (HAS-A relationships)
class Director:
    def __init__(self, name, birth_year, nationality='Unknown'):
        self.name = name
        self.birth_year = birth_year
        self.nationality = nationality

    def __str__(self):
        return f'{self.name} ({self.nationality})'

class Actor:
    def __init__(self, name, birth_year):
        self.name = name
        self.birth_year = birth_year

    def __str__(self):
        return self.name

# Base class
class Movie:
    def __init__(self, title, year, director, genre='Unknown'):
        self.title = title
        self.year = year
        self.director = director  # HAS-A Director (composition)
        self.genre = genre
        self.cast = []           # HAS-A list of Actors (composition)
        self.rating = None

    def add_actor(self, actor):
        self.cast.append(actor)

    def set_rating(self, rating):
        if 0 <= rating <= 10:
            self.rating = rating
        else:
            print('Rating must be between 0 and 10')

    def play(self):
        return f'Now playing: {self.title}'

    def get_info(self):
        info = f'\n=== {self.title} ({self.year}) ===\n'
        info += f'Genre: {self.genre}\n'
        info += f'Director: {self.director}\n'
        if self.cast:
            info += 'Cast: {'.join(str(actor) for actor in self.cast) + '\n'
        if self.rating:
            info += f'Rating: {self.rating}/10\n'
        return info

    def __str__(self):
        return f'{self.title} ({self.year})'

# Inheritance classes (IS-A relationships)
class ActionMovie(Movie):
    def __init__(self, title, year, director, lead_actor, budget_millions):
        super().__init__(title, year, director, 'Action')
        self.lead_actor = lead_actor
        self.budget_millions = budget_millions

    def play(self):  # Override parent method
        return f'Now playing: {self.title}'

    def explosive_scene(self):
        return f'{self.lead_actor} performs a stunt sequence'

class DocumentaryMovie(Movie):
    def __init__(self, title, year, director, subject, duration_minutes):
        super().__init__(title, year, director, 'Documentary')
        self.subject = subject
        self.duration_minutes = duration_minutes

    def play(self):  # Override parent method
        return f'Now presenting: {self.title}'

    def get_educational_value(self):
        return f'Learn about {self.subject} in {self.duration_minutes} minutes'

class StreamingMovie(Movie):
    def __init__(self, title, year, director, platform, subscription_required=True):
        super().__init__(title, year, director)
        self.platform = platform
        self.subscription_required = subscription_required

    def stream(self):
        if self.subscription_required:
            return f'Subscribe to {self.platform} to watch {self.title}'
        else:
            return f'Now streaming {self.title} on {self.platform}'

# Example usage
def main():
    # Create Directors and Actors (for composition - HAS-A)
    nolan = Director('Christopher Nolan', 1970, 'British')
    dicaprio = Actor('Leonardo DiCaprio', 1974)
    hardy = Actor('Tom Hardy', 1977)

    spielberg = Director('Steven Spielberg', 1946, 'American')

    # Create different types of movies (demonstrating inheritance - IS-A)

    inception = ActionMovie('Inception', 2010, nolan, 'Leonardo DiCaprio', 160)
    inception.add_actor(dicaprio)  # Movie HAS-A Actor
    inception.add_actor(hardy)     # Movie HAS-A Actor
    inception.set_rating(8.8)

    earth_doc = DocumentaryMovie('Planet Earth', 2006, 
                                Director('David Attenborough', 1926, 'British'),
                                'Wildlife and Nature', 550)
    earth_doc.set_rating(9.4)

    streaming_film = StreamingMovie('The Social Dilemma', 2020,
                                   Director('Jeff Orlowski', 1981, 'American'),
                                   'Netflix', True)
    streaming_film.set_rating(7.6)

    movies = [inception, earth_doc, streaming_film]

    print('> Polymorphism - Same method, different behaviors')
    for movie in movies:
        print(f'  {movie.play()}')

    print(inception.get_info())
    print(f'Action Bonus: {inception.explosive_scene()}')

    print(earth_doc.get_info())
    print(f'Educational: {earth_doc.get_educational_value()}')

    print(streaming_film.get_info())
    print(f'Streaming: {streaming_film.stream()}')

if __name__ == '__main__':
    main()
→ This page was created with help from Claude AI.