python-by-example/08_oop/03_inheritance.py
2025-12-30 08:50:00 +02:00

437 lines
12 KiB
Python

"""
================================================================================
File: 03_inheritance.py
Topic: Inheritance in Python
================================================================================
This file demonstrates inheritance in Python, a fundamental OOP concept that
allows classes to inherit attributes and methods from other classes. This
promotes code reuse and establishes relationships between classes.
Key Concepts:
- Single inheritance
- Method overriding
- super() function
- Multiple inheritance
- Method Resolution Order (MRO)
- Abstract base classes
================================================================================
"""
# -----------------------------------------------------------------------------
# 1. Basic Inheritance
# -----------------------------------------------------------------------------
# Child class inherits from parent class
print("--- Basic Inheritance ---")
class Animal:
"""Base class for all animals."""
def __init__(self, name):
"""Initialize animal with a name."""
self.name = name
def speak(self):
"""Make a generic sound."""
return "Some generic sound"
def describe(self):
"""Describe the animal."""
return f"I am {self.name}"
class Dog(Animal):
"""Dog class inheriting from Animal."""
pass # Inherits everything from Animal
# Dog has all Animal's methods
buddy = Dog("Buddy")
print(buddy.describe())
print(f"Says: {buddy.speak()}")
print(f"Dog is instance of Animal: {isinstance(buddy, Animal)}")
# -----------------------------------------------------------------------------
# 2. Method Overriding
# -----------------------------------------------------------------------------
# Child class can override parent methods
print("\n--- Method Overriding ---")
class Cat(Animal):
"""Cat class with overridden method."""
def speak(self):
"""Cats meow instead of generic sound."""
return "Meow!"
class Cow(Animal):
"""Cow class with overridden method."""
def speak(self):
"""Cows moo."""
return "Moo!"
# Each animal speaks differently
animals = [Dog("Rex"), Cat("Whiskers"), Cow("Bessie")]
print("Each animal speaks:")
for animal in animals:
print(f" {animal.name}: {animal.speak()}")
# -----------------------------------------------------------------------------
# 3. Extending Parent Class with super()
# -----------------------------------------------------------------------------
# Use super() to call parent methods
print("\n--- Using super() ---")
class Bird(Animal):
"""Bird class extending Animal."""
def __init__(self, name, can_fly=True):
"""Initialize bird with flying ability."""
super().__init__(name) # Call parent's __init__
self.can_fly = can_fly
def speak(self):
"""Birds chirp."""
return "Chirp!"
def describe(self):
"""Extend parent's describe method."""
base = super().describe() # Get parent's description
fly_status = "can fly" if self.can_fly else "cannot fly"
return f"{base} and I {fly_status}"
sparrow = Bird("Sparrow", can_fly=True)
penguin = Bird("Penny", can_fly=False)
print(sparrow.describe())
print(penguin.describe())
# -----------------------------------------------------------------------------
# 4. Inheritance Chain
# -----------------------------------------------------------------------------
# Classes can inherit from other child classes
print("\n--- Inheritance Chain ---")
class Vehicle:
"""Base vehicle class."""
def __init__(self, brand):
self.brand = brand
def start(self):
return "Vehicle starting..."
class Car(Vehicle):
"""Car inherits from Vehicle."""
def __init__(self, brand, model):
super().__init__(brand)
self.model = model
def start(self):
return f"{self.brand} {self.model} engine starting..."
class ElectricCar(Car):
"""ElectricCar inherits from Car."""
def __init__(self, brand, model, battery_kw):
super().__init__(brand, model)
self.battery_kw = battery_kw
def start(self):
return f"{self.brand} {self.model} powering up silently... (Battery: {self.battery_kw}kW)"
def charge(self):
return f"Charging {self.battery_kw}kW battery..."
# Create instances
regular_car = Car("Toyota", "Camry")
electric_car = ElectricCar("Tesla", "Model 3", 75)
print(regular_car.start())
print(electric_car.start())
print(electric_car.charge())
# Check inheritance
print(f"\nElectricCar is Car: {isinstance(electric_car, Car)}")
print(f"ElectricCar is Vehicle: {isinstance(electric_car, Vehicle)}")
# -----------------------------------------------------------------------------
# 5. Multiple Inheritance
# -----------------------------------------------------------------------------
# A class can inherit from multiple parent classes
print("\n--- Multiple Inheritance ---")
class Flyable:
"""Mixin class for flying ability."""
def fly(self):
return f"{self.name} is flying!"
def land(self):
return f"{self.name} has landed."
class Swimmable:
"""Mixin class for swimming ability."""
def swim(self):
return f"{self.name} is swimming!"
def dive(self):
return f"{self.name} dives underwater."
class Duck(Animal, Flyable, Swimmable):
"""Duck can do everything!"""
def speak(self):
return "Quack!"
# Duck has methods from all parent classes
duck = Duck("Donald")
print(f"{duck.name} says: {duck.speak()}")
print(duck.fly())
print(duck.swim())
print(duck.describe())
# -----------------------------------------------------------------------------
# 6. Method Resolution Order (MRO)
# -----------------------------------------------------------------------------
# Python determines method lookup order
print("\n--- Method Resolution Order (MRO) ---")
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
class E(B, C):
def method(self):
return "E"
# Check MRO
print("MRO for class D:")
for cls in D.__mro__:
print(f" {cls.__name__}")
d = D()
e = E()
print(f"\nd.method(): {d.method()}") # Returns "B" (first in MRO after D)
print(f"e.method(): {e.method()}") # Returns "E" (overridden)
# -----------------------------------------------------------------------------
# 7. Calling Methods from Specific Parent
# -----------------------------------------------------------------------------
print("\n--- Calling Specific Parent Methods ---")
class Parent1:
def greet(self):
return "Hello from Parent1"
class Parent2:
def greet(self):
return "Hello from Parent2"
class Child(Parent1, Parent2):
def greet(self):
return "Hello from Child"
def greet_all(self):
"""Call all parent greet methods."""
return [
f"Child: {self.greet()}",
f"Parent1: {Parent1.greet(self)}",
f"Parent2: {Parent2.greet(self)}"
]
child = Child()
print("Calling all greet methods:")
for greeting in child.greet_all():
print(f" {greeting}")
# -----------------------------------------------------------------------------
# 8. Abstract Base Classes
# -----------------------------------------------------------------------------
# Define interfaces that must be implemented
print("\n--- Abstract Base Classes ---")
from abc import ABC, abstractmethod
class Shape(ABC):
"""Abstract base class for shapes."""
@abstractmethod
def area(self):
"""Calculate area - must be implemented."""
pass
@abstractmethod
def perimeter(self):
"""Calculate perimeter - must be implemented."""
pass
def describe(self):
"""Non-abstract method - can be used as-is."""
return f"A shape with area {self.area():.2f}"
class Rectangle(Shape):
"""Concrete rectangle class."""
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
"""Concrete circle class."""
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
def perimeter(self):
import math
return 2 * math.pi * self.radius
# Cannot instantiate abstract class
# shape = Shape() # TypeError!
# Concrete classes work fine
rect = Rectangle(5, 3)
circle = Circle(4)
print(f"Rectangle: area={rect.area()}, perimeter={rect.perimeter()}")
print(f"Circle: area={circle.area():.2f}, perimeter={circle.perimeter():.2f}")
print(rect.describe())
# -----------------------------------------------------------------------------
# 9. Using isinstance() and issubclass()
# -----------------------------------------------------------------------------
print("\n--- Type Checking ---")
class Mammal(Animal):
pass
class Reptile(Animal):
pass
class DogV2(Mammal):
def speak(self):
return "Woof!"
class Snake(Reptile):
def speak(self):
return "Hiss!"
dog = DogV2("Rex")
snake = Snake("Slinky")
# isinstance() checks if object is instance of class
print("isinstance checks:")
print(f" dog isinstance of DogV2: {isinstance(dog, DogV2)}")
print(f" dog isinstance of Mammal: {isinstance(dog, Mammal)}")
print(f" dog isinstance of Animal: {isinstance(dog, Animal)}")
print(f" dog isinstance of Reptile: {isinstance(dog, Reptile)}")
# issubclass() checks class relationships
print("\nissubclass checks:")
print(f" DogV2 issubclass of Mammal: {issubclass(DogV2, Mammal)}")
print(f" DogV2 issubclass of Animal: {issubclass(DogV2, Animal)}")
print(f" DogV2 issubclass of Reptile: {issubclass(DogV2, Reptile)}")
# -----------------------------------------------------------------------------
# 10. Practical Example: Employee Hierarchy
# -----------------------------------------------------------------------------
print("\n--- Practical Example: Employee Hierarchy ---")
class Employee:
"""Base employee class."""
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self._salary = salary
@property
def salary(self):
return self._salary
def get_annual_salary(self):
return self._salary * 12
def __str__(self):
return f"{self.name} (ID: {self.employee_id})"
class Manager(Employee):
"""Manager with team and bonus."""
def __init__(self, name, employee_id, salary, team_size=0):
super().__init__(name, employee_id, salary)
self.team_size = team_size
def get_annual_salary(self):
# Managers get bonus based on team size
base = super().get_annual_salary()
bonus = self.team_size * 1000 # $1000 per team member
return base + bonus
def __str__(self):
return f"Manager {super().__str__()} - Team: {self.team_size}"
class Developer(Employee):
"""Developer with tech stack."""
def __init__(self, name, employee_id, salary, languages):
super().__init__(name, employee_id, salary)
self.languages = languages
def get_annual_salary(self):
# Developers get extra for each language
base = super().get_annual_salary()
language_bonus = len(self.languages) * 500 # $500 per language
return base + language_bonus
def __str__(self):
langs = ", ".join(self.languages)
return f"Developer {super().__str__()} - Languages: {langs}"
# Create employees
manager = Manager("Alice", "M001", 8000, team_size=5)
dev1 = Developer("Bob", "D001", 6000, ["Python", "JavaScript", "SQL"])
dev2 = Developer("Charlie", "D002", 5500, ["Python"])
employees = [manager, dev1, dev2]
print("Employee Details:")
for emp in employees:
print(f" {emp}")
print(f" Annual Salary: ${emp.get_annual_salary():,}")