You have Employee with name and salary. Manager needs the same plus a team list. Instead of duplicating code, Manager extends Employee - inheriting its attributes and methods while adding its own.

Basic inheritance

Create a subclass that extends a parent.

example
basic_inheritance.py
Replay: real traced execution (multi-file project)
# Basic Inheritance

class Animal:
    """Base class for all animals."""

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} makes a sound"

    def describe(self):
        return f"I am {self.name}"


# Dog inherits from Animal
class Dog(Animal):
    """Dog class inherits from Animal."""
    pass  # No additional code - inherits everything


# Cat inherits from Animal
class Cat(Animal):
    """Cat class inherits from Animal."""
    pass


# Bird with additional attribute
class Bird(Animal):
    def __init__(self, name, can_fly=True):
        # Call parent's __init__
        super().__init__(name)
        self.can_fly = can_fly


# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")

# Create Dog - uses inherited __init__
dog = Dog("Buddy")
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")

print()

# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")

print()

# Create Bird - has additional attribute
bird = Bird("Tweety")
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")

print()

# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")

print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")

# Basic Inheritance

class Animal:
    """Base class for all animals."""

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} makes a sound"

    def describe(self):
        return f"I am {self.name}"


# Dog inherits from Animal
class Dog(Animal):
    """Dog class inherits from Animal."""
    pass  # No additional code - inherits everything


# Cat inherits from Animal
class Cat(Animal):
    """Cat class inherits from Animal."""
    pass


# Bird with additional attribute
class Bird(Animal):
    def __init__(self, name, can_fly=True):
        # Call parent's __init__
        super().__init__(name)
        self.can_fly = can_fly


# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")

# Create Dog - uses inherited __init__
dog = Dog("Rex")
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")

print()

# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")

print()

# Create Bird - has additional attribute
bird = Bird("Tweety")
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")

print()

# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")

print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")

# Basic Inheritance

class Animal:
    """Base class for all animals."""

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} makes a sound"

    def describe(self):
        return f"I am {self.name}"


# Dog inherits from Animal
class Dog(Animal):
    """Dog class inherits from Animal."""
    pass  # No additional code - inherits everything


# Cat inherits from Animal
class Cat(Animal):
    """Cat class inherits from Animal."""
    pass


# Bird with additional attribute
class Bird(Animal):
    def __init__(self, name, can_fly=True):
        # Call parent's __init__
        super().__init__(name)
        self.can_fly = can_fly


# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")

# Create Dog - uses inherited __init__
dog = Dog("Luna")
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")

print()

# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")

print()

# Create Bird - has additional attribute
bird = Bird("Tweety")
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")

print()

# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")

print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")

# Basic Inheritance

class Animal:
    """Base class for all animals."""

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} makes a sound"

    def describe(self):
        return f"I am {self.name}"


# Dog inherits from Animal
class Dog(Animal):
    """Dog class inherits from Animal."""
    pass  # No additional code - inherits everything


# Cat inherits from Animal
class Cat(Animal):
    """Cat class inherits from Animal."""
    pass


# Bird with additional attribute
class Bird(Animal):
    def __init__(self, name, can_fly=True):
        # Call parent's __init__
        super().__init__(name)
        self.can_fly = can_fly


# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")

# Create Dog - uses inherited __init__
dog = Dog("Buddy")
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")

print()

# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")

print()

# Create Bird - has additional attribute
bird = Bird("Robin")
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")

print()

# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")

print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")

# Basic Inheritance

class Animal:
    """Base class for all animals."""

    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} makes a sound"

    def describe(self):
        return f"I am {self.name}"


# Dog inherits from Animal
class Dog(Animal):
    """Dog class inherits from Animal."""
    pass  # No additional code - inherits everything


# Cat inherits from Animal
class Cat(Animal):
    """Cat class inherits from Animal."""
    pass


# Bird with additional attribute
class Bird(Animal):
    def __init__(self, name, can_fly=True):
        # Call parent's __init__
        super().__init__(name)
        self.can_fly = can_fly


# Demonstrate basic inheritance
print("=== Basic Inheritance ===\n")

# Create Dog - uses inherited __init__
dog = Dog("Buddy")
print(f"Dog name: {dog.name}")
print(f"Dog speaks: {dog.speak()}")
print(f"Dog describes: {dog.describe()}")

print()

# Create Cat - also inherits everything
cat = Cat("Whiskers")
print(f"Cat name: {cat.name}")
print(f"Cat speaks: {cat.speak()}")
print(f"Cat describes: {cat.describe()}")

print()

# Create Bird - has additional attribute
bird = Bird("Kiwi", can_fly=False)
print(f"Bird name: {bird.name}")
print(f"Bird can fly: {bird.can_fly}")
print(f"Bird speaks: {bird.speak()}")

print()

# Bird that can't fly
penguin = Bird("Pingu", can_fly=False)
print(f"Penguin name: {penguin.name}")
print(f"Penguin can fly: {penguin.can_fly}")

print("\n=== Inheritance Rules ===")
print("""
1. Child inherits all attributes and methods
2. Syntax: class Child(Parent):
3. super().__init__() calls parent's __init__
4. Child can add new attributes/methods
5. 'pass' means no additions (pure inheritance)
""")

  1. """Base class for all animals."""

    3class Animal: #?animalclass4    """Base class for all animals."""5    6    def __init__(self, name): #?initmethod7        self.name = name #?storename8    9    def speak(self): #?speakmethod10        return f"{self.name} makes a sound"11    12    def describe(self): #?describemethod13        return f"I am {self.name}"141516# Dog inherits from Animal #?dogclass17class Dog(Animal): #?dogdefinition18    """Dog class inherits from Animal."""19    pass  # No additional code - inherits everything #?passstatement202122# Cat inherits from Animal23class Cat(Animal): #?catclass24    """Cat class inherits from Animal."""25    pass262728# Bird with additional attribute #?birdclass29class Bird(Animal): #?birddefinition30    def __init__(self, name, can_fly=True): #?birdinitn31        # Call parent's __init__ #?callsuper32        super().__init__(name) #?superinit33        self.can_fly = can_fly #?extraattribute343536# Demonstrate basic inheritance37print("=== Basic Inheritance ===\n")3839# Create Dog - uses inherited __init__ #?createdog40dog = Dog("Buddy") #?doginstance41#@dog=Dog("Rex"), Dog("Luna")
    output=== Basic Inheritance ===
  2. self.name ← Buddy

    pass 1 of 4
    6def __init__(self⟨Dog A⟩, nameBuddy): #?initmethod7    self.name→ Buddy = nameBuddy #?storename
    All 4 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Dog A⟩BuddyBuddy
    2⟨Cat B⟩WhiskersWhiskers
    3⟨Bird C⟩TweetyTweety
    4⟨Bird D⟩PinguPingu
  3. dog ← ⟨Dog A⟩

    39# Create Dog - uses inherited __init__ #?createdog40dog→ ⟨Dog A⟩ = Dog("Buddy") #?doginstance41#@dog=Dog("Rex"), Dog("Luna")42print(f"Dog name: {dog.nameBuddy}") #?accessattribute43print(f"Dog speaks: {dog⟨Dog A⟩.speak()}") #?callmethod44print(f"Dog describes: {dog.describe()}")
    outputDog name: Buddy
  4. def speak(self): #?speakmethod

    pass 1 of 3
    9def speak(self⟨Dog A⟩): #?speakmethod10    return f"{self.nameBuddy} makes a sound"
    All 3 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Buddy
    2⟨Cat B⟩Whiskers
    3⟨Bird C⟩Tweety
  5. print(f"Dog speaks: {dog.speak()}") #?callmethod

    42print(f"Dog name: {dog.name}") #?accessattribute43print(f"Dog speaks: {dog⟨Dog A⟩.speak()}") #?callmethod44print(f"Dog describes: {dog⟨Dog A⟩.describe()}")
    outputDog speaks: Buddy makes a sound
  6. def describe(self): #?describemethod

    pass 1 of 2
    12def describe(self⟨Dog A⟩): #?describemethod13    return f"I am {self.nameBuddy}"
  7. print(f"Dog describes: {dog.describe()}")

    43print(f"Dog speaks: {dog.speak()}") #?callmethod44print(f"Dog describes: {dog⟨Dog A⟩.describe()}")4546print()4748# Create Cat - also inherits everything #?createcat49cat = Cat("Whiskers")50print(f"Cat name: {cat.name}")
    outputDog describes: I am Buddy
  8. cat ← ⟨Cat B⟩

    48# Create Cat - also inherits everything #?createcat49cat→ ⟨Cat B⟩ = Cat("Whiskers")50print(f"Cat name: {cat.nameWhiskers}")51print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")52print(f"Cat describes: {cat.describe()}")
    outputCat name: Whiskers
  9. print(f"Cat speaks: {cat.speak()}")

    50print(f"Cat name: {cat.name}")51print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")52print(f"Cat describes: {cat⟨Cat B⟩.describe()}")
    outputCat speaks: Whiskers makes a sound
  10. def describe(self): #?describemethod

    pass 2 of 2
    12def describe(self⟨Cat B⟩): #?describemethod13    return f"I am {self.nameWhiskers}"
  11. print(f"Cat describes: {cat.describe()}")

    51print(f"Cat speaks: {cat.speak()}")52print(f"Cat describes: {cat⟨Cat B⟩.describe()}")5354print()5556# Create Bird - has additional attribute #?createbird57bird = Bird("Tweety") #?birdinstance58#@bird=Bird("Robin"), Bird("Kiwi", can_fly=False)
    outputCat describes: I am Whiskers
  12. def __init__(self, name, can_fly=True): #?birdinitn # Call par…

    pass 1 of 2
    29class Bird(Animal): #?birddefinition30    def __init__(self⟨Bird C⟩, nameTweety, can_flyTrue=TrueTrue): #?birdinitn31        # Call parent's __init__ #?callsuper32        super().__init__(name) #?superinit33        self.can_fly = can_fly #?extraattribute
  13. self.can_fly ← True

    32super().__init__(name) #?superinit33self.can_fly→ True = can_flyTrue #?extraattribute
  14. bird ← ⟨Bird C⟩

    56# Create Bird - has additional attribute #?createbird57bird→ ⟨Bird C⟩ = Bird("Tweety") #?birdinstance58#@bird=Bird("Robin"), Bird("Kiwi", can_fly=False)59print(f"Bird name: {bird.nameTweety}")60print(f"Bird can fly: {bird.can_flyTrue}")61print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")
    outputBird name: Tweety
    Bird can fly: True
  15. print(f"Bird speaks: {bird.speak()}")

    60print(f"Bird can fly: {bird.can_fly}")61print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")6263print()6465# Bird that can't fly #?flightlessbird66penguin = Bird("Pingu", can_fly=False) #?penguininstance67print(f"Penguin name: {penguin.name}")
    outputBird speaks: Tweety makes a sound
  16. def __init__(self, name, can_fly=True): #?birdinitn # Call par…

    pass 2 of 2
    29class Bird(Animal): #?birddefinition30    def __init__(self⟨Bird D⟩, namePingu, can_flyFalse=TrueTrue): #?birdinitn31        # Call parent's __init__ #?callsuper32        super().__init__(name) #?superinit33        self.can_fly = can_fly #?extraattribute
  17. self.can_fly ← False

    32super().__init__(name) #?superinit33self.can_fly→ False = can_flyFalse #?extraattribute
  18. penguin ← ⟨Bird D⟩

    65# Bird that can't fly #?flightlessbird66penguin→ ⟨Bird D⟩ = Bird("Pingu", can_fly=False) #?penguininstance67print(f"Penguin name: {penguin.namePingu}")68print(f"Penguin can fly: {penguin.can_flyFalse}")6970print("\n=== Inheritance Rules ===")71print("""721. Child inherits all attributes and methods732. Syntax: class Child(Parent):743. super().__init__() calls parent's __init__754. Child can add new attributes/methods765. 'pass' means no additions (pure inheritance)77""")
    outputPenguin name: Pingu
    Penguin can fly: False
    
    === Inheritance Rules ===
    
    1. Child inherits all attributes and methods
    2. Syntax: class Child(Parent):
    3. super().__init__() calls parent's __init__
    4. Child can add new attributes/methods
    5. 'pass' means no additions (pure inheritance)
  1. """Base class for all animals."""

    3class Animal:4    """Base class for all animals."""5    6    def __init__(self, name):7        self.name = name8    9    def speak(self):10        return f"{self.name} makes a sound"11    12    def describe(self):13        return f"I am {self.name}"141516# Dog inherits from Animal17class Dog(Animal):18    """Dog class inherits from Animal."""19    pass  # No additional code - inherits everything202122# Cat inherits from Animal23class Cat(Animal):24    """Cat class inherits from Animal."""25    pass262728# Bird with additional attribute29class Bird(Animal):30    def __init__(self, name, can_fly=True):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly343536# Demonstrate basic inheritance37print("=== Basic Inheritance ===\n")3839# Create Dog - uses inherited __init__40dog = Dog("Rex")41print(f"Dog name: {dog.name}")
    output=== Basic Inheritance ===
  2. self.name ← Rex

    pass 1 of 4
    6def __init__(self⟨Dog A⟩, nameRex):7    self.name→ Rex = nameRex
    All 4 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Dog A⟩RexRex
    2⟨Cat B⟩WhiskersWhiskers
    3⟨Bird C⟩TweetyTweety
    4⟨Bird D⟩PinguPingu
  3. dog ← ⟨Dog A⟩

    39# Create Dog - uses inherited __init__40dog→ ⟨Dog A⟩ = Dog("Rex")41print(f"Dog name: {dog.nameRex}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog.describe()}")
    outputDog name: Rex
  4. def speak(self):

    pass 1 of 3
    9def speak(self⟨Dog A⟩):10    return f"{self.nameRex} makes a sound"
    All 3 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Rex
    2⟨Cat B⟩Whiskers
    3⟨Bird C⟩Tweety
  5. print(f"Dog speaks: {dog.speak()}")

    41print(f"Dog name: {dog.name}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")
    outputDog speaks: Rex makes a sound
  6. def describe(self):

    pass 1 of 2
    12def describe(self⟨Dog A⟩):13    return f"I am {self.nameRex}"
  7. print(f"Dog describes: {dog.describe()}")

    42print(f"Dog speaks: {dog.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")4445print()4647# Create Cat - also inherits everything48cat = Cat("Whiskers")49print(f"Cat name: {cat.name}")
    outputDog describes: I am Rex
  8. cat ← ⟨Cat B⟩

    47# Create Cat - also inherits everything48cat→ ⟨Cat B⟩ = Cat("Whiskers")49print(f"Cat name: {cat.nameWhiskers}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat.describe()}")
    outputCat name: Whiskers
  9. print(f"Cat speaks: {cat.speak()}")

    49print(f"Cat name: {cat.name}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")
    outputCat speaks: Whiskers makes a sound
  10. def describe(self):

    pass 2 of 2
    12def describe(self⟨Cat B⟩):13    return f"I am {self.nameWhiskers}"
  11. print(f"Cat describes: {cat.describe()}")

    50print(f"Cat speaks: {cat.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")5253print()5455# Create Bird - has additional attribute56bird = Bird("Tweety")57print(f"Bird name: {bird.name}")
    outputCat describes: I am Whiskers
  12. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 1 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird C⟩, nameTweety, can_flyTrue=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  13. self.can_fly ← True

    32super().__init__(name)33self.can_fly→ True = can_flyTrue
  14. bird ← ⟨Bird C⟩

    55# Create Bird - has additional attribute56bird→ ⟨Bird C⟩ = Bird("Tweety")57print(f"Bird name: {bird.nameTweety}")58print(f"Bird can fly: {bird.can_flyTrue}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")
    outputBird name: Tweety
    Bird can fly: True
  15. print(f"Bird speaks: {bird.speak()}")

    58print(f"Bird can fly: {bird.can_fly}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")6061print()6263# Bird that can't fly64penguin = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.name}")
    outputBird speaks: Tweety makes a sound
  16. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 2 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird D⟩, namePingu, can_flyFalse=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  17. self.can_fly ← False

    32super().__init__(name)33self.can_fly→ False = can_flyFalse
  18. penguin ← ⟨Bird D⟩

    63# Bird that can't fly64penguin→ ⟨Bird D⟩ = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.namePingu}")66print(f"Penguin can fly: {penguin.can_flyFalse}")6768print("\n=== Inheritance Rules ===")69print("""701. Child inherits all attributes and methods712. Syntax: class Child(Parent):723. super().__init__() calls parent's __init__734. Child can add new attributes/methods745. 'pass' means no additions (pure inheritance)75""")
    outputPenguin name: Pingu
    Penguin can fly: False
    
    === Inheritance Rules ===
    
    1. Child inherits all attributes and methods
    2. Syntax: class Child(Parent):
    3. super().__init__() calls parent's __init__
    4. Child can add new attributes/methods
    5. 'pass' means no additions (pure inheritance)
  1. """Base class for all animals."""

    3class Animal:4    """Base class for all animals."""5    6    def __init__(self, name):7        self.name = name8    9    def speak(self):10        return f"{self.name} makes a sound"11    12    def describe(self):13        return f"I am {self.name}"141516# Dog inherits from Animal17class Dog(Animal):18    """Dog class inherits from Animal."""19    pass  # No additional code - inherits everything202122# Cat inherits from Animal23class Cat(Animal):24    """Cat class inherits from Animal."""25    pass262728# Bird with additional attribute29class Bird(Animal):30    def __init__(self, name, can_fly=True):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly343536# Demonstrate basic inheritance37print("=== Basic Inheritance ===\n")3839# Create Dog - uses inherited __init__40dog = Dog("Luna")41print(f"Dog name: {dog.name}")
    output=== Basic Inheritance ===
  2. self.name ← Luna

    pass 1 of 4
    6def __init__(self⟨Dog A⟩, nameLuna):7    self.name→ Luna = nameLuna
    All 4 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Dog A⟩LunaLuna
    2⟨Cat B⟩WhiskersWhiskers
    3⟨Bird C⟩TweetyTweety
    4⟨Bird D⟩PinguPingu
  3. dog ← ⟨Dog A⟩

    39# Create Dog - uses inherited __init__40dog→ ⟨Dog A⟩ = Dog("Luna")41print(f"Dog name: {dog.nameLuna}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog.describe()}")
    outputDog name: Luna
  4. def speak(self):

    pass 1 of 3
    9def speak(self⟨Dog A⟩):10    return f"{self.nameLuna} makes a sound"
    All 3 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Luna
    2⟨Cat B⟩Whiskers
    3⟨Bird C⟩Tweety
  5. print(f"Dog speaks: {dog.speak()}")

    41print(f"Dog name: {dog.name}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")
    outputDog speaks: Luna makes a sound
  6. def describe(self):

    pass 1 of 2
    12def describe(self⟨Dog A⟩):13    return f"I am {self.nameLuna}"
  7. print(f"Dog describes: {dog.describe()}")

    42print(f"Dog speaks: {dog.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")4445print()4647# Create Cat - also inherits everything48cat = Cat("Whiskers")49print(f"Cat name: {cat.name}")
    outputDog describes: I am Luna
  8. cat ← ⟨Cat B⟩

    47# Create Cat - also inherits everything48cat→ ⟨Cat B⟩ = Cat("Whiskers")49print(f"Cat name: {cat.nameWhiskers}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat.describe()}")
    outputCat name: Whiskers
  9. print(f"Cat speaks: {cat.speak()}")

    49print(f"Cat name: {cat.name}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")
    outputCat speaks: Whiskers makes a sound
  10. def describe(self):

    pass 2 of 2
    12def describe(self⟨Cat B⟩):13    return f"I am {self.nameWhiskers}"
  11. print(f"Cat describes: {cat.describe()}")

    50print(f"Cat speaks: {cat.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")5253print()5455# Create Bird - has additional attribute56bird = Bird("Tweety")57print(f"Bird name: {bird.name}")
    outputCat describes: I am Whiskers
  12. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 1 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird C⟩, nameTweety, can_flyTrue=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  13. self.can_fly ← True

    32super().__init__(name)33self.can_fly→ True = can_flyTrue
  14. bird ← ⟨Bird C⟩

    55# Create Bird - has additional attribute56bird→ ⟨Bird C⟩ = Bird("Tweety")57print(f"Bird name: {bird.nameTweety}")58print(f"Bird can fly: {bird.can_flyTrue}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")
    outputBird name: Tweety
    Bird can fly: True
  15. print(f"Bird speaks: {bird.speak()}")

    58print(f"Bird can fly: {bird.can_fly}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")6061print()6263# Bird that can't fly64penguin = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.name}")
    outputBird speaks: Tweety makes a sound
  16. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 2 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird D⟩, namePingu, can_flyFalse=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  17. self.can_fly ← False

    32super().__init__(name)33self.can_fly→ False = can_flyFalse
  18. penguin ← ⟨Bird D⟩

    63# Bird that can't fly64penguin→ ⟨Bird D⟩ = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.namePingu}")66print(f"Penguin can fly: {penguin.can_flyFalse}")6768print("\n=== Inheritance Rules ===")69print("""701. Child inherits all attributes and methods712. Syntax: class Child(Parent):723. super().__init__() calls parent's __init__734. Child can add new attributes/methods745. 'pass' means no additions (pure inheritance)75""")
    outputPenguin name: Pingu
    Penguin can fly: False
    
    === Inheritance Rules ===
    
    1. Child inherits all attributes and methods
    2. Syntax: class Child(Parent):
    3. super().__init__() calls parent's __init__
    4. Child can add new attributes/methods
    5. 'pass' means no additions (pure inheritance)
  1. """Base class for all animals."""

    3class Animal:4    """Base class for all animals."""5    6    def __init__(self, name):7        self.name = name8    9    def speak(self):10        return f"{self.name} makes a sound"11    12    def describe(self):13        return f"I am {self.name}"141516# Dog inherits from Animal17class Dog(Animal):18    """Dog class inherits from Animal."""19    pass  # No additional code - inherits everything202122# Cat inherits from Animal23class Cat(Animal):24    """Cat class inherits from Animal."""25    pass262728# Bird with additional attribute29class Bird(Animal):30    def __init__(self, name, can_fly=True):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly343536# Demonstrate basic inheritance37print("=== Basic Inheritance ===\n")3839# Create Dog - uses inherited __init__40dog = Dog("Buddy")41print(f"Dog name: {dog.name}")
    output=== Basic Inheritance ===
  2. self.name ← Buddy

    pass 1 of 4
    6def __init__(self⟨Dog A⟩, nameBuddy):7    self.name→ Buddy = nameBuddy
    All 4 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Dog A⟩BuddyBuddy
    2⟨Cat B⟩WhiskersWhiskers
    3⟨Bird C⟩RobinRobin
    4⟨Bird D⟩PinguPingu
  3. dog ← ⟨Dog A⟩

    39# Create Dog - uses inherited __init__40dog→ ⟨Dog A⟩ = Dog("Buddy")41print(f"Dog name: {dog.nameBuddy}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog.describe()}")
    outputDog name: Buddy
  4. def speak(self):

    pass 1 of 3
    9def speak(self⟨Dog A⟩):10    return f"{self.nameBuddy} makes a sound"
    All 3 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Buddy
    2⟨Cat B⟩Whiskers
    3⟨Bird C⟩Robin
  5. print(f"Dog speaks: {dog.speak()}")

    41print(f"Dog name: {dog.name}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")
    outputDog speaks: Buddy makes a sound
  6. def describe(self):

    pass 1 of 2
    12def describe(self⟨Dog A⟩):13    return f"I am {self.nameBuddy}"
  7. print(f"Dog describes: {dog.describe()}")

    42print(f"Dog speaks: {dog.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")4445print()4647# Create Cat - also inherits everything48cat = Cat("Whiskers")49print(f"Cat name: {cat.name}")
    outputDog describes: I am Buddy
  8. cat ← ⟨Cat B⟩

    47# Create Cat - also inherits everything48cat→ ⟨Cat B⟩ = Cat("Whiskers")49print(f"Cat name: {cat.nameWhiskers}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat.describe()}")
    outputCat name: Whiskers
  9. print(f"Cat speaks: {cat.speak()}")

    49print(f"Cat name: {cat.name}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")
    outputCat speaks: Whiskers makes a sound
  10. def describe(self):

    pass 2 of 2
    12def describe(self⟨Cat B⟩):13    return f"I am {self.nameWhiskers}"
  11. print(f"Cat describes: {cat.describe()}")

    50print(f"Cat speaks: {cat.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")5253print()5455# Create Bird - has additional attribute56bird = Bird("Robin")57print(f"Bird name: {bird.name}")
    outputCat describes: I am Whiskers
  12. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 1 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird C⟩, nameRobin, can_flyTrue=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  13. self.can_fly ← True

    32super().__init__(name)33self.can_fly→ True = can_flyTrue
  14. bird ← ⟨Bird C⟩

    55# Create Bird - has additional attribute56bird→ ⟨Bird C⟩ = Bird("Robin")57print(f"Bird name: {bird.nameRobin}")58print(f"Bird can fly: {bird.can_flyTrue}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")
    outputBird name: Robin
    Bird can fly: True
  15. print(f"Bird speaks: {bird.speak()}")

    58print(f"Bird can fly: {bird.can_fly}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")6061print()6263# Bird that can't fly64penguin = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.name}")
    outputBird speaks: Robin makes a sound
  16. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 2 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird D⟩, namePingu, can_flyFalse=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  17. self.can_fly ← False

    32super().__init__(name)33self.can_fly→ False = can_flyFalse
  18. penguin ← ⟨Bird D⟩

    63# Bird that can't fly64penguin→ ⟨Bird D⟩ = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.namePingu}")66print(f"Penguin can fly: {penguin.can_flyFalse}")6768print("\n=== Inheritance Rules ===")69print("""701. Child inherits all attributes and methods712. Syntax: class Child(Parent):723. super().__init__() calls parent's __init__734. Child can add new attributes/methods745. 'pass' means no additions (pure inheritance)75""")
    outputPenguin name: Pingu
    Penguin can fly: False
    
    === Inheritance Rules ===
    
    1. Child inherits all attributes and methods
    2. Syntax: class Child(Parent):
    3. super().__init__() calls parent's __init__
    4. Child can add new attributes/methods
    5. 'pass' means no additions (pure inheritance)
  1. """Base class for all animals."""

    3class Animal:4    """Base class for all animals."""5    6    def __init__(self, name):7        self.name = name8    9    def speak(self):10        return f"{self.name} makes a sound"11    12    def describe(self):13        return f"I am {self.name}"141516# Dog inherits from Animal17class Dog(Animal):18    """Dog class inherits from Animal."""19    pass  # No additional code - inherits everything202122# Cat inherits from Animal23class Cat(Animal):24    """Cat class inherits from Animal."""25    pass262728# Bird with additional attribute29class Bird(Animal):30    def __init__(self, name, can_fly=True):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly343536# Demonstrate basic inheritance37print("=== Basic Inheritance ===\n")3839# Create Dog - uses inherited __init__40dog = Dog("Buddy")41print(f"Dog name: {dog.name}")
    output=== Basic Inheritance ===
  2. self.name ← Buddy

    pass 1 of 4
    6def __init__(self⟨Dog A⟩, nameBuddy):7    self.name→ Buddy = nameBuddy
    All 4 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Dog A⟩BuddyBuddy
    2⟨Cat B⟩WhiskersWhiskers
    3⟨Bird C⟩KiwiKiwi
    4⟨Bird D⟩PinguPingu
  3. dog ← ⟨Dog A⟩

    39# Create Dog - uses inherited __init__40dog→ ⟨Dog A⟩ = Dog("Buddy")41print(f"Dog name: {dog.nameBuddy}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog.describe()}")
    outputDog name: Buddy
  4. def speak(self):

    pass 1 of 3
    9def speak(self⟨Dog A⟩):10    return f"{self.nameBuddy} makes a sound"
    All 3 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Buddy
    2⟨Cat B⟩Whiskers
    3⟨Bird C⟩Kiwi
  5. print(f"Dog speaks: {dog.speak()}")

    41print(f"Dog name: {dog.name}")42print(f"Dog speaks: {dog⟨Dog A⟩.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")
    outputDog speaks: Buddy makes a sound
  6. def describe(self):

    pass 1 of 2
    12def describe(self⟨Dog A⟩):13    return f"I am {self.nameBuddy}"
  7. print(f"Dog describes: {dog.describe()}")

    42print(f"Dog speaks: {dog.speak()}")43print(f"Dog describes: {dog⟨Dog A⟩.describe()}")4445print()4647# Create Cat - also inherits everything48cat = Cat("Whiskers")49print(f"Cat name: {cat.name}")
    outputDog describes: I am Buddy
  8. cat ← ⟨Cat B⟩

    47# Create Cat - also inherits everything48cat→ ⟨Cat B⟩ = Cat("Whiskers")49print(f"Cat name: {cat.nameWhiskers}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat.describe()}")
    outputCat name: Whiskers
  9. print(f"Cat speaks: {cat.speak()}")

    49print(f"Cat name: {cat.name}")50print(f"Cat speaks: {cat⟨Cat B⟩.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")
    outputCat speaks: Whiskers makes a sound
  10. def describe(self):

    pass 2 of 2
    12def describe(self⟨Cat B⟩):13    return f"I am {self.nameWhiskers}"
  11. print(f"Cat describes: {cat.describe()}")

    50print(f"Cat speaks: {cat.speak()}")51print(f"Cat describes: {cat⟨Cat B⟩.describe()}")5253print()5455# Create Bird - has additional attribute56bird = Bird("Kiwi", can_fly=False)57print(f"Bird name: {bird.name}")
    outputCat describes: I am Whiskers
  12. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 1 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird C⟩, nameKiwi, can_flyFalse=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  13. self.can_fly ← False

    32super().__init__(name)33self.can_fly→ False = can_flyFalse
  14. bird ← ⟨Bird C⟩

    55# Create Bird - has additional attribute56bird→ ⟨Bird C⟩ = Bird("Kiwi", can_fly=False)57print(f"Bird name: {bird.nameKiwi}")58print(f"Bird can fly: {bird.can_flyFalse}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")
    outputBird name: Kiwi
    Bird can fly: False
  15. print(f"Bird speaks: {bird.speak()}")

    58print(f"Bird can fly: {bird.can_fly}")59print(f"Bird speaks: {bird⟨Bird C⟩.speak()}")6061print()6263# Bird that can't fly64penguin = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.name}")
    outputBird speaks: Kiwi makes a sound
  16. def __init__(self, name, can_fly=True): # Call parent's __init…

    pass 2 of 2
    29class Bird(Animal):30    def __init__(self⟨Bird D⟩, namePingu, can_flyFalse=TrueTrue):31        # Call parent's __init__32        super().__init__(name)33        self.can_fly = can_fly
  17. self.can_fly ← False

    32super().__init__(name)33self.can_fly→ False = can_flyFalse
  18. penguin ← ⟨Bird D⟩

    63# Bird that can't fly64penguin→ ⟨Bird D⟩ = Bird("Pingu", can_fly=False)65print(f"Penguin name: {penguin.namePingu}")66print(f"Penguin can fly: {penguin.can_flyFalse}")6768print("\n=== Inheritance Rules ===")69print("""701. Child inherits all attributes and methods712. Syntax: class Child(Parent):723. super().__init__() calls parent's __init__734. Child can add new attributes/methods745. 'pass' means no additions (pure inheritance)75""")
    outputPenguin name: Pingu
    Penguin can fly: False
    
    === Inheritance Rules ===
    
    1. Child inherits all attributes and methods
    2. Syntax: class Child(Parent):
    3. super().__init__() calls parent's __init__
    4. Child can add new attributes/methods
    5. 'pass' means no additions (pure inheritance)

class Child(Parent): inherits all of Parent's methods and attributes.

inheritance Subclass gets parent's methods and attributes. Add or override as needed.

Call parent constructor

Initialize parent attributes with super().

super_init.py
Replay: real traced execution (multi-file project)
# Using super() to Call Parent's __init__

class Person:
    """Base class for people."""

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(f"  Person.__init__ called: {name}, {age}")

    def introduce(self):
        return f"I'm {self.name}, {self.age} years old"


class Student(Person):
    """Student inherits from Person."""

    def __init__(self, name, age, student_id):
        print(f"  Student.__init__ starting...")
        super().__init__(name, age)
        self.student_id = student_id
        print(f"  Student.__init__ completed: id={student_id}")

    def introduce(self):
        return f"I'm {self.name}, student #{self.student_id}"


class Employee(Person):
    """Employee inherits from Person."""

    def __init__(self, name, age, department, salary):
        super().__init__(name, age)
        self.department = department
        self.salary = salary

    def introduce(self):
        return f"I'm {self.name} from {self.department}"


class Manager(Employee):
    """Manager inherits from Employee (which inherits from Person)."""

    def __init__(self, name, age, department, salary, team_size):
        # super() calls Employee.__init__
        super().__init__(name, age, department, salary)
        self.team_size = team_size

    def introduce(self):
        return f"I'm {self.name}, managing {self.team_size} people in {self.department}"


print("=== Understanding super().__init__() ===\n")

# Simple case: Student
print("Creating Student:")
student = Student("Alice", 20, "S12345")
print(f"Result: {student.introduce()}")
print(f"Has name: {student.name}")
print(f"Has age: {student.age}")
print(f"Has student_id: {student.student_id}")

print("\n" + "="*40 + "\n")

# Employee case
print("Creating Employee:")
emp = Employee("Bob", 35, "Engineering", 75000)
print(f"Result: {emp.introduce()}")
print(f"Has name: {emp.name}")
print(f"Has age: {emp.age}")
print(f"Has department: {emp.department}")
print(f"Has salary: {emp.salary}")

print("\n" + "="*40 + "\n")

# Inheritance chain: Manager -> Employee -> Person
print("Creating Manager (three-level inheritance):")
manager = Manager("Carol", 45, "Engineering", 120000, 8)
print(f"Result: {manager.introduce()}")
print(f"Has name: {manager.name} (from Person)")
print(f"Has age: {manager.age} (from Person)")
print(f"Has department: {manager.department} (from Employee)")
print(f"Has salary: {manager.salary} (from Employee)")
print(f"Has team_size: {manager.team_size} (from Manager)")

print("\n=== super() Key Points ===")
print("""
1. super() returns a proxy to the parent class
2. super().__init__() calls parent's constructor
3. Must pass required arguments to parent
4. Each level in chain calls its parent
5. Attributes accumulate through the chain:
   Manager has: name, age (Person)
              + department, salary (Employee)
              + team_size (Manager)
""")

  1. """Base class for people."""

    3class Person: #?personclass4    """Base class for people."""5    6    def __init__(self, name, age): #?personinitf7        self.name = name #?nameattr8        self.age = age #?ageattr9        print(f"  Person.__init__ called: {name}, {age}")10    11    def introduce(self): #?introduce12        return f"I'm {self.name}, {self.age} years old"131415class Student(Person): #?studentclass16    """Student inherits from Person."""17    18    def __init__(self, name, age, student_id): #?studentinitf19        print(f"  Student.__init__ starting...")20        super().__init__(name, age) #?superinit21        self.student_id = student_id #?studentidattr22        print(f"  Student.__init__ completed: id={student_id}")23    24    def introduce(self): #?studentintroduce25        return f"I'm {self.name}, student #{self.student_id}"262728class Employee(Person): #?employeeclass29    """Employee inherits from Person."""30    31    def __init__(self, name, age, department, salary): #?employeeinit32        super().__init__(name, age) #?employeesuper33        self.department = department #?deptattr34        self.salary = salary #?salaryattr35    36    def introduce(self):37        return f"I'm {self.name} from {self.department}"383940class Manager(Employee): #?managerclass41    """Manager inherits from Employee (which inherits from Person)."""42    43    def __init__(self, name, age, department, salary, team_size): #?managerinit44        # super() calls Employee.__init__ #?managersuper45        super().__init__(name, age, department, salary) #?superchain46        self.team_size = team_size #?teamsizeattr47    48    def introduce(self):49        return f"I'm {self.name}, managing {self.team_size} people in {self.department}"505152print("=== Understanding super().__init__() ===\n")5354# Simple case: Student #?studentdemo55print("Creating Student:")56student = Student("Alice", 20, "S12345") #?studentinstance57print(f"Result: {student.introduce()}")
    output=== Understanding super().__init__() ===
    Creating Student:
  2. def __init__(self, name, age, student_id): #?studentinitf

    18def __init__(self⟨Student A⟩, nameAlice, age20, student_idS12345): #?studentinitf19    print(f"  Student.__init__ starting...")20    super().__init__(name, age) #?superinit
    output  Student.__init__ starting...
  3. self.name ← Alice, self.age ← 20

    pass 1 of 3
    6def __init__(self⟨Student A⟩, nameAlice, age20): #?personinitf7    self.name→ Alice = nameAlice #?nameattr8    self.age→ 20 = age20 #?ageattr9    print(f"  Person.__init__ called: {nameAlice}, {age20}")
    output  Person.__init__ called: Alice, 20
    All 3 passes — pass 1 is the card above
    passselfnameageself.nameself.age
    1⟨Student A⟩Alice20Alice20
    2⟨Employee B⟩Bob35Bob35
    3⟨Manager C⟩Carol45Carol45
  4. self.student_id ← S12345

    20super().__init__(name, age) #?superinit21self.student_id→ S12345 = student_idS12345 #?studentidattr22print(f"  Student.__init__ completed: id={student_idS12345}")
    output  Student.__init__ completed: id=S12345
  5. student ← ⟨Student A⟩

    55print("Creating Student:")56student→ ⟨Student A⟩ = Student("Alice", 20, "S12345") #?studentinstance57print(f"Result: {student⟨Student A⟩.introduce()}")58print(f"Has name: {student.name}")
  6. def introduce(self): #?studentintroduce

    24def introduce(self⟨Student A⟩): #?studentintroduce25    return f"I'm {self.nameAlice}, student #{self.student_idS12345}"
  7. print(f"Result: {student.introduce()}")

    56student = Student("Alice", 20, "S12345") #?studentinstance57print(f"Result: {student⟨Student A⟩.introduce()}")58print(f"Has name: {student.nameAlice}")59print(f"Has age: {student.age20}")60print(f"Has student_id: {student.student_idS12345}")6162print("\n" + "="*40 + "\n")6364# Employee case #?employeedemo65print("Creating Employee:")66emp = Employee("Bob", 35, "Engineering", 75000)67print(f"Result: {emp.introduce()}")
    outputResult: I'm Alice, student #S12345
    Has name: Alice
    Has age: 20
    Has student_id: S12345
    
    ========================================
    Creating Employee:
  8. def __init__(self, name, age, department, salary): #?employeeinit

    pass 1 of 2
    31def __init__(self⟨Employee B⟩, nameBob, age35, departmentEngineering, salary75000): #?employeeinit32    super().__init__(name, age) #?employeesuper33    self.department = department #?deptattr
  9. self.department ← Engineering, self.salary ← 75000

    32super().__init__(name, age) #?employeesuper33self.department→ Engineering = departmentEngineering #?deptattr34self.salary→ 75000 = salary75000 #?salaryattr
  10. emp ← ⟨Employee B⟩

    65print("Creating Employee:")66emp→ ⟨Employee B⟩ = Employee("Bob", 35, "Engineering", 75000)67print(f"Result: {emp⟨Employee B⟩.introduce()}")68print(f"Has name: {emp.name}")
  11. def introduce(self):

    36def introduce(self⟨Employee B⟩):37    return f"I'm {self.nameBob} from {self.departmentEngineering}"
  12. print(f"Result: {emp.introduce()}")

    66emp = Employee("Bob", 35, "Engineering", 75000)67print(f"Result: {emp⟨Employee B⟩.introduce()}")68print(f"Has name: {emp.nameBob}")69print(f"Has age: {emp.age35}")70print(f"Has department: {emp.departmentEngineering}")71print(f"Has salary: {emp.salary75000}")7273print("\n" + "="*40 + "\n")7475# Inheritance chain: Manager -> Employee -> Person #?managerdemo76print("Creating Manager (three-level inheritance):")77manager = Manager("Carol", 45, "Engineering", 120000, 8) #?managerinstance78print(f"Result: {manager.introduce()}")
    outputResult: I'm Bob from Engineering
    Has name: Bob
    Has age: 35
    Has department: Engineering
    Has salary: 75000
    
    ========================================
    Creating Manager (three-level inheritance):
  13. def __init__(self, name, age, department, salary, team_size): #?manage…

    43def __init__(self⟨Manager C⟩, nameCarol, age45, departmentEngineering, salary120000, team_size8): #?managerinit44    # super() calls Employee.__init__ #?managersuper45    super().__init__(name, age, department, salary) #?superchain46    self.team_size = team_size #?teamsizeattr
  14. def __init__(self, name, age, department, salary): #?employeeinit

    pass 2 of 2
    31def __init__(self⟨Manager C⟩, nameCarol, age45, departmentEngineering, salary120000): #?employeeinit32    super().__init__(name, age) #?employeesuper33    self.department = department #?deptattr
  15. self.department ← Engineering, self.salary ← 120000

    32super().__init__(name, age) #?employeesuper33self.department→ Engineering = departmentEngineering #?deptattr34self.salary→ 120000 = salary120000 #?salaryattr
  16. self.team_size ← 8

    45super().__init__(name, age, department, salary) #?superchain46self.team_size→ 8 = team_size8 #?teamsizeattr
  17. manager ← ⟨Manager C⟩

    76print("Creating Manager (three-level inheritance):")77manager→ ⟨Manager C⟩ = Manager("Carol", 45, "Engineering", 120000, 8) #?managerinstance78print(f"Result: {manager⟨Manager C⟩.introduce()}")79print(f"Has name: {manager.name} (from Person)")
  18. def introduce(self):

    48def introduce(self⟨Manager C⟩):49    return f"I'm {self.nameCarol}, managing {self.team_size8} people in {self.departmentEngineering}"
  19. print(f"Result: {manager.introduce()}")

    77manager = Manager("Carol", 45, "Engineering", 120000, 8) #?managerinstance78print(f"Result: {manager⟨Manager C⟩.introduce()}")79print(f"Has name: {manager.nameCarol} (from Person)")80print(f"Has age: {manager.age45} (from Person)")81print(f"Has department: {manager.departmentEngineering} (from Employee)")82print(f"Has salary: {manager.salary120000} (from Employee)")83print(f"Has team_size: {manager.team_size8} (from Manager)")8485print("\n=== super() Key Points ===")86print("""871. super() returns a proxy to the parent class882. super().__init__() calls parent's constructor893. Must pass required arguments to parent904. Each level in chain calls its parent915. Attributes accumulate through the chain:92   Manager has: name, age (Person)93              + department, salary (Employee)94              + team_size (Manager)95""")
    outputResult: I'm Carol, managing 8 people in Engineering
    Has name: Carol (from Person)
    Has age: 45 (from Person)
    Has department: Engineering (from Employee)
    Has salary: 120000 (from Employee)
    Has team_size: 8 (from Manager)
    
    === super() Key Points ===
    
    1. super() returns a proxy to the parent class
    2. super().__init__() calls parent's constructor
    3. Must pass required arguments to parent
    4. Each level in chain calls its parent
    5. Attributes accumulate through the chain:
       Manager has: name, age (Person)
                  + department, salary (Employee)
                  + team_size (Manager)

super().__init__(args) calls parent's __init__. Always call it first.

super() Access parent class: `super().__init__()` calls parent constructor.

Override methods

Replace parent behavior with child-specific implementation.

method_override.py
Replay: real traced execution (multi-file project)
# Method Overriding

class Shape:
    """Base class for geometric shapes."""

    def __init__(self, name):
        self.name = name

    def area(self):
        return 0  # Default: no area

    def perimeter(self):
        return 0  # Default: no perimeter

    def describe(self):
        return f"I am a {self.name}"


class Rectangle(Shape):
    """Rectangle overrides area and perimeter."""

    def __init__(self, width, height):
        super().__init__("Rectangle")
        self.width = width
        self.height = height

    # Override area method
    def area(self):
        return self.width * self.height

    # Override perimeter method
    def perimeter(self):
        return 2 * (self.width + self.height)

    # Override describe
    def describe(self):
        return f"Rectangle: {self.width} x {self.height}"


class Circle(Shape):
    """Circle overrides area and perimeter."""

    PI = 3.14159

    def __init__(self, radius):
        super().__init__("Circle")
        self.radius = radius

    def area(self):
        return Circle.PI * self.radius ** 2

    def perimeter(self):
        return 2 * Circle.PI * self.radius

    def describe(self):
        return f"Circle: radius = {self.radius}"


class Triangle(Shape):
    """Triangle overrides methods."""

    def __init__(self, a, b, c):
        super().__init__("Triangle")
        self.a = a  # Side lengths
        self.b = b
        self.c = c

    def area(self):
        # Heron's formula
        s = (self.a + self.b + self.c) / 2
        return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5

    def perimeter(self):
        return self.a + self.b + self.c

    def describe(self):
        return f"Triangle: sides {self.a}, {self.b}, {self.c}"


# Demonstration
print("=== Method Overriding ===\n")

# Create shapes
shapes = [
    Rectangle(5, 3),
    Circle(4),
    Triangle(3, 4, 5),
]

# Polymorphic behavior - same method, different results
for shape in shapes:
    print(shape.describe())
    print(f"  Area: {shape.area():.2f}")
    print(f"  Perimeter: {shape.perimeter():.2f}")
    print()

# The base Shape class
print("Base Shape class (no override):")
generic = Shape("Unknown")
print(f"  {generic.describe()}")
print(f"  Area: {generic.area()}")
print(f"  Perimeter: {generic.perimeter()}")

print("\n=== Override Rules ===")
print("""
1. Same method name as parent
2. Same parameters (usually)
3. Different implementation
4. Child's version is called on child objects
5. Parent's version still exists in parent objects
6. No special keyword needed (unlike Java's @Override)
""")

  1. PI ← (empty)

    3class Shape: #?shapeclass4    """Base class for geometric shapes."""5    6    def __init__(self, name): #?shapeinit7        self.name = name8    9    def area(self): #?shapearea10        return 0  # Default: no area11    12    def perimeter(self): #?shapeperimeter13        return 0  # Default: no perimeter14    15    def describe(self): #?shapedescribe16        return f"I am a {self.name}"171819class Rectangle(Shape): #?rectangleclass20    """Rectangle overrides area and perimeter."""21    22    def __init__(self, width, height): #?rectinit23        super().__init__("Rectangle") #?rectsuper24        self.width = width25        self.height = height26    27    # Override area method #?overridearea28    def area(self): #?rectarea29        return self.width * self.height30    31    # Override perimeter method #?overrideperimeter32    def perimeter(self): #?rectperimeter33        return 2 * (self.width + self.height)34    35    # Override describe #?overridedescribe36    def describe(self): #?rectdescribe37        return f"Rectangle: {self.width} x {self.height}"383940class Circle(Shape): #?circleclass41    """Circle overrides area and perimeter."""42    43    PI→ (empty) = 3.14159 #?piconstant44    45    def __init__(self, radius):46        super().__init__("Circle")47        self.radius = radius48    49    def area(self): #?circlearea50        return Circle.PI * self.radius ** 251    52    def perimeter(self): #?circleperimeter53        return 2 * Circle.PI * self.radius54    55    def describe(self):56        return f"Circle: radius = {self.radius}"575859class Triangle(Shape): #?triangleclass60    """Triangle overrides methods."""61    62    def __init__(self, a, b, c): #?triangleinit63        super().__init__("Triangle")64        self.a = a  # Side lengths65        self.b = b66        self.c = c67    68    def area(self): #?trianglearea69        # Heron's formula70        s = (self.a + self.b + self.c) / 2 #?herons71        return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.572    73    def perimeter(self):74        return self.a + self.b + self.c75    76    def describe(self):77        return f"Triangle: sides {self.a}, {self.b}, {self.c}"787980# Demonstration81print("=== Method Overriding ===\n")8283# Create shapes #?createshapes84shapes = [ #?shapelist85    Rectangle(5, 3),86    Circle(4),87    Triangle(3, 4, 5),88]
    output=== Method Overriding ===
  2. def __init__(self, width, height): #?rectinit

    22def __init__(self⟨Rectangle A⟩, width5, height3): #?rectinit23    super().__init__("Rectangle") #?rectsuper24    self.width = width
  3. self.name ← Rectangle

    pass 1 of 4
    6def __init__(self⟨Rectangle A⟩, nameRectangle): #?shapeinit7    self.name→ Rectangle = nameRectangle
    All 4 passes — pass 1 is the card above
    passselfnameself.name
    1⟨Rectangle A⟩RectangleRectangle
    2⟨Circle B⟩CircleCircle
    3⟨Triangle C⟩TriangleTriangle
    4⟨Shape D⟩UnknownUnknown
  4. self.width ← 5, self.height ← 3

    23super().__init__("Rectangle") #?rectsuper24self.width→ 5 = width525self.height→ 3 = height3
  5. def __init__(self, radius):

    45def __init__(self⟨Circle B⟩, radius4):46    super().__init__("Circle")47    self.radius = radius
  6. self.radius ← 4

    46super().__init__("Circle")47self.radius→ 4 = radius4
  7. def __init__(self, a, b, c): #?triangleinit

    62def __init__(self⟨Triangle C⟩, a3, b4, c5): #?triangleinit63    super().__init__("Triangle")64    self.a = a  # Side lengths
  8. self.a ← 3, self.b ← 4, self.c ← 5

    63super().__init__("Triangle")64self.a→ 3 = a3  # Side lengths65self.b→ 4 = b466self.c→ 5 = c5
  9. shapes ← [⟨Rectangle A⟩, ⟨Circle B⟩, ⟨Triangle C⟩]

    83# Create shapes #?createshapes84shapes→ [⟨Rectangle A⟩, ⟨Circle B⟩, ⟨Triangle C⟩] = [ #?shapelist85    Rectangle(5, 3),86    Circle(4),87    Triangle(3, 4, 5),88]
  10. for shape in shapes: #?iterateshapes

    pass 1 of 3
    90# Polymorphic behavior - same method, different results #?polymorphism91for shape⟨Rectangle A⟩ in shapes[⟨Rectangle A⟩, ⟨Circle B⟩, ⟨Triangle C⟩]: #?iterateshapes92    print(shape⟨Rectangle A⟩.describe()) #?calldescribe93    print(f"  Area: {shape.area():.2f}") #?callarea
    All 3 passes — pass 1 is the card above
    passshapeselfself.widthself.heightself.radiusself.aself.bself.c
    1⟨Rectangle A⟩⟨Rectangle A⟩53
    2⟨Circle B⟩⟨Circle B⟩4
    3⟨Triangle C⟩⟨Triangle C⟩345
  11. def describe(self): #?rectdescribe

    35# Override describe #?overridedescribe36def describe(self⟨Rectangle A⟩): #?rectdescribe37    return f"Rectangle: {self.width5} x {self.height3}"
  12. print(shape.describe()) #?calldescribe

    91for shape in shapes: #?iterateshapes92    print(shape⟨Rectangle A⟩.describe()) #?calldescribe93    print(f"  Area: {shape⟨Rectangle A⟩.area():.2f}") #?callarea94    print(f"  Perimeter: {shape.perimeter():.2f}") #?callperimeter
    outputRectangle: 5 x 3
  13. def area(self): #?rectarea

    27# Override area method #?overridearea28def area(self⟨Rectangle A⟩): #?rectarea29    return self.width5 * self.height3
  14. print(f" Area: {shape.area():.2f}") #?callarea

    92print(shape.describe()) #?calldescribe93print(f"  Area: {shape⟨Rectangle A⟩.area():.2f}") #?callarea94print(f"  Perimeter: {shape⟨Rectangle A⟩.perimeter():.2f}") #?callperimeter95print()
    output  Area: 15.00
  15. def perimeter(self): #?rectperimeter

    31# Override perimeter method #?overrideperimeter32def perimeter(self⟨Rectangle A⟩): #?rectperimeter33    return 2 * (self.width5 + self.height3)
  16. print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter

    93print(f"  Area: {shape.area():.2f}") #?callarea94print(f"  Perimeter: {shape⟨Rectangle A⟩.perimeter():.2f}") #?callperimeter95print()
    output  Perimeter: 16.00
  17. def describe(self):

    55def describe(self⟨Circle B⟩):56    return f"Circle: radius = {self.radius4}"
  18. print(shape.describe()) #?calldescribe

    91for shape in shapes: #?iterateshapes92    print(shape⟨Circle B⟩.describe()) #?calldescribe93    print(f"  Area: {shape⟨Circle B⟩.area():.2f}") #?callarea94    print(f"  Perimeter: {shape.perimeter():.2f}") #?callperimeter
    outputCircle: radius = 4
  19. def area(self): #?circlearea

    49def area(self⟨Circle B⟩): #?circlearea50    return Circle.PI3.14159 * self.radius4 ** 2
  20. print(f" Area: {shape.area():.2f}") #?callarea

    92print(shape.describe()) #?calldescribe93print(f"  Area: {shape⟨Circle B⟩.area():.2f}") #?callarea94print(f"  Perimeter: {shape⟨Circle B⟩.perimeter():.2f}") #?callperimeter95print()
    output  Area: 50.27
  21. def perimeter(self): #?circleperimeter

    52def perimeter(self⟨Circle B⟩): #?circleperimeter53    return 2 * Circle.PI3.14159 * self.radius4
  22. print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter

    93print(f"  Area: {shape.area():.2f}") #?callarea94print(f"  Perimeter: {shape⟨Circle B⟩.perimeter():.2f}") #?callperimeter95print()
    output  Perimeter: 25.13
  23. def describe(self):

    76def describe(self⟨Triangle C⟩):77    return f"Triangle: sides {self.a3}, {self.b4}, {self.c5}"
  24. print(shape.describe()) #?calldescribe

    91for shape in shapes: #?iterateshapes92    print(shape⟨Triangle C⟩.describe()) #?calldescribe93    print(f"  Area: {shape⟨Triangle C⟩.area():.2f}") #?callarea94    print(f"  Perimeter: {shape.perimeter():.2f}") #?callperimeter
    outputTriangle: sides 3, 4, 5
  25. s ← 6.0

    68def area(self⟨Triangle C⟩): #?trianglearea69    # Heron's formula70    s→ 6.0 = (self.a3 + self.b4 + self.c5) / 2 #?herons71    return (s6.0 * (s - self.a3) * (s - self.b4) * (s - self.c5)) ** 0.5
  26. print(f" Area: {shape.area():.2f}") #?callarea

    92print(shape.describe()) #?calldescribe93print(f"  Area: {shape⟨Triangle C⟩.area():.2f}") #?callarea94print(f"  Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}") #?callperimeter95print()
    output  Area: 6.00
  27. def perimeter(self):

    73def perimeter(self⟨Triangle C⟩):74    return self.a3 + self.b4 + self.c5
  28. print(f" Perimeter: {shape.perimeter():.2f}") #?callperimeter

    93print(f"  Area: {shape.area():.2f}") #?callarea94print(f"  Perimeter: {shape⟨Triangle C⟩.perimeter():.2f}") #?callperimeter95print()
    output  Perimeter: 12.00
  29. print("Base Shape class (no override):")

    97# The base Shape class #?baseshape98print("Base Shape class (no override):")99generic = Shape("Unknown") #?genericshape100print(f"  {generic.describe()}")
    outputBase Shape class (no override):
  30. generic ← ⟨Shape D⟩

    98print("Base Shape class (no override):")99generic→ ⟨Shape D⟩ = Shape("Unknown") #?genericshape100print(f"  {generic⟨Shape D⟩.describe()}")101print(f"  Area: {generic.area()}")
  31. def describe(self): #?shapedescribe

    15def describe(self⟨Shape D⟩): #?shapedescribe16    return f"I am a {self.nameUnknown}"
  32. print(f" {generic.describe()}")

    99generic = Shape("Unknown") #?genericshape100print(f"  {generic⟨Shape D⟩.describe()}")101print(f"  Area: {generic⟨Shape D⟩.area()}")102print(f"  Perimeter: {generic.perimeter()}")
    output  I am a Unknown
  33. def area(self): #?shapearea

    9def area(self⟨Shape D⟩): #?shapearea10    return 0  # Default: no area
  34. print(f" Area: {generic.area()}")

    100print(f"  {generic.describe()}")101print(f"  Area: {generic⟨Shape D⟩.area()}")102print(f"  Perimeter: {generic⟨Shape D⟩.perimeter()}")
    output  Area: 0
  35. def perimeter(self): #?shapeperimeter

    12def perimeter(self⟨Shape D⟩): #?shapeperimeter13    return 0  # Default: no perimeter
  36. print(f" Perimeter: {generic.perimeter()}")

    101print(f"  Area: {generic.area()}")102print(f"  Perimeter: {generic⟨Shape D⟩.perimeter()}")103104print("\n=== Override Rules ===")105print("""1061. Same method name as parent1072. Same parameters (usually)1083. Different implementation1094. Child's version is called on child objects1105. Parent's version still exists in parent objects1116. No special keyword needed (unlike Java's @Override)112""")
    output  Perimeter: 0
    
    === Override Rules ===
    
    1. Same method name as parent
    2. Same parameters (usually)
    3. Different implementation
    4. Child's version is called on child objects
    5. Parent's version still exists in parent objects
    6. No special keyword needed (unlike Java's @Override)

Same method name in child replaces parent's version.

override Child provides its own version of parent method. Same name and signature.

Extend parent methods

Add to parent behavior instead of replacing it.

extend_method.py
Replay: real traced execution (multi-file project)
# Extending Parent Methods with super()

class Logger:
    """Base logger class."""

    def __init__(self):
        self.logs = []

    def log(self, message):
        """Basic logging - just store message."""
        entry = f"LOG: {message}"
        self.logs.append(entry)
        return entry


class TimestampLogger(Logger):
    """Logger that adds timestamps."""

    def log(self, message):
        # Add timestamp, then call parent
        from datetime import datetime
        timestamp = datetime.now().strftime("%H:%M:%S")
        timestamped_message = f"[{timestamp}] {message}"

        # Call parent's log with enhanced message
        return super().log(timestamped_message)


class LevelLogger(Logger):
    """Logger with severity levels."""

    def log(self, message, level="INFO"):
        # Add level prefix, then call parent
        leveled_message = f"{level}: {message}"
        return super().log(leveled_message)

    # Convenience methods
    def info(self, message):
        return self.log(message, "INFO")

    def warning(self, message):
        return self.log(message, "WARNING")

    def error(self, message):
        return self.log(message, "ERROR")


class CountingLogger(Logger):
    """Logger that counts messages."""

    def __init__(self):
        super().__init__()
        self.count = 0

    def log(self, message):
        self.count += 1
        # Extend message with count
        numbered_message = f"#{self.count} {message}"
        return super().log(numbered_message)


class FullFeaturedLogger(Logger):
    """Logger combining multiple features."""

    def __init__(self, name):
        super().__init__()
        self.name = name
        self.count = 0

    def log(self, message, level="INFO"):
        from datetime import datetime
        self.count += 1

        # Build full formatted message
        timestamp = datetime.now().strftime("%H:%M:%S")
        formatted = f"[{timestamp}] [{self.name}] #{self.count} {level}: {message}"

        # Still use parent's storage mechanism
        return super().log(formatted)


# Demonstration
print("=== Extending Parent Methods ===\n")

# Basic logger
print("--- Basic Logger ---")
basic = Logger()
print(basic.log("Hello"))
print(basic.log("World"))
print(f"Total logs: {len(basic.logs)}")

print("\n--- Timestamp Logger ---")
ts_logger = TimestampLogger()
print(ts_logger.log("Application started"))
print(ts_logger.log("Processing data"))
print(f"Logs stored: {ts_logger.logs}")

print("\n--- Level Logger ---")
level_logger = LevelLogger()
print(level_logger.info("Normal operation"))
print(level_logger.warning("Low disk space"))
print(level_logger.error("Connection failed"))

print("\n--- Counting Logger ---")
count_logger = CountingLogger()
print(count_logger.log("First message"))
print(count_logger.log("Second message"))
print(count_logger.log("Third message"))
print(f"Total count: {count_logger.count}")

print("\n--- Full Featured Logger ---")
app_logger = FullFeaturedLogger("APP")
print(app_logger.log("Started", "INFO"))
print(app_logger.log("Processing", "DEBUG"))
print(app_logger.log("Something wrong", "ERROR"))

print("\n=== Extend vs Override ===")
print("""
OVERRIDE: Replace parent's behavior completely
    def method(self):
        # all new code

EXTEND: Add to parent's behavior
    def method(self):
        # do something extra
        super().method()  # then call parent
        # optionally do more

Benefits of extending:
1. Reuse parent's logic
2. Add functionality without duplicating code
3. Parent changes automatically apply
4. Can pre-process OR post-process
""")

  1. """Base logger class."""

    3class Logger: #?loggerclass4    """Base logger class."""5    6    def __init__(self): #?loggerinit7        self.logs = [] #?logslist8    9    def log(self, message): #?logmethod10        """Basic logging - just store message."""11        entry = f"LOG: {message}" #?basicentry12        self.logs.append(entry)13        return entry141516class TimestampLogger(Logger): #?timestampclass17    """Logger that adds timestamps."""18    19    def log(self, message): #?timestamplog20        # Add timestamp, then call parent #?addtimestamp21        from datetime import datetime22        timestamp = datetime.now().strftime("%H:%M:%S") #?gettimestamp23        timestamped_message = f"[{timestamp}] {message}" #?formatmsg24        25        # Call parent's log with enhanced message #?callparent26        return super().log(timestamped_message) #?superlog272829class LevelLogger(Logger): #?levelclass30    """Logger with severity levels."""31    32    def log(self, message, level="INFO"): #?levellog33        # Add level prefix, then call parent #?addlevel34        leveled_message = f"{level}: {message}" #?levelmsg35        return super().log(leveled_message)36    37    # Convenience methods #?convenience38    def info(self, message): #?infomethod39        return self.log(message, "INFO")40    41    def warning(self, message): #?warningmethod42        return self.log(message, "WARNING")43    44    def error(self, message): #?errormethod45        return self.log(message, "ERROR")464748class CountingLogger(Logger): #?countingclass49    """Logger that counts messages."""50    51    def __init__(self): #?countinginit52        super().__init__() #?superinit53        self.count = 0 #?initcount54    55    def log(self, message): #?countinglog56        self.count += 1 #?increment57        # Extend message with count #?extendcount58        numbered_message = f"#{self.count} {message}"59        return super().log(numbered_message)606162class FullFeaturedLogger(Logger): #?fullclass63    """Logger combining multiple features."""64    65    def __init__(self, name): #?fullinit66        super().__init__()67        self.name = name68        self.count = 069    70    def log(self, message, level="INFO"): #?fulllog71        from datetime import datetime72        self.count += 173        74        # Build full formatted message #?buildmsg75        timestamp = datetime.now().strftime("%H:%M:%S")76        formatted = f"[{timestamp}] [{self.name}] #{self.count} {level}: {message}"77        78        # Still use parent's storage mechanism #?useparent79        return super().log(formatted)808182# Demonstration83print("=== Extending Parent Methods ===\n")8485# Basic logger #?basicdemo86print("--- Basic Logger ---")87basic = Logger() #?createbasic88print(basic.log("Hello"))
    output=== Extending Parent Methods ===
    --- Basic Logger ---
  2. self.logs ← []

    pass 1 of 5
    6def __init__(self⟨Logger A⟩): #?loggerinit7    self.logs→ [] = [] #?logslist
    All 5 passes — pass 1 is the card above
    passselfself.logs
    1⟨Logger A⟩[]
    2⟨TimestampLogger B⟩[]
    3⟨LevelLogger C⟩[]
    4⟨CountingLogger D⟩[]
    5⟨FullFeaturedLogger E⟩[]
  3. basic ← ⟨Logger A⟩

    86print("--- Basic Logger ---")87basic→ ⟨Logger A⟩ = Logger() #?createbasic88print(basic⟨Logger A⟩.log("Hello"))89print(basic.log("World"))
  4. entry ← LOG: Hello, self.logs ← ['LOG: Hello']

    pass 1 of 13
    9def log(self⟨Logger A⟩, messageHello): #?logmethod10    """Basic logging - just store message."""11    entry→ LOG: Hello = f"LOG: {messageHello}" #?basicentry12    self.logs→ ['LOG: Hello'].append(entryLOG: Hello)13    return entryLOG: Hello
    13 passes — pass 1 is the card above
    passselfmessageentryself.logs
    1⟨Logger A⟩HelloLOG: Hello[] ['LOG: Hello']
    2⟨Logger A⟩WorldLOG: World['LOG: Hello'] ['LOG: Hello', 'LOG: World']
    3⟨TimestampLogger B⟩[22:54:22] Application startedLOG: [22:54:22] Application started[] ['LOG: [22:54:22] Application started']
    4⟨TimestampLogger B⟩[22:54:22] Processing dataLOG: [22:54:22] Processing data['LOG: [22:54:22] Application started'] ['LOG: [22:54:22] Application started', 'LOG: [22:54:22] Processing data']
    5⟨LevelLogger C⟩INFO: Normal operationLOG: INFO: Normal operation[] ['LOG: INFO: Normal operation']
    6⟨LevelLogger C⟩WARNING: Low disk spaceLOG: WARNING: Low disk space['LOG: INFO: Normal operation'] ['LOG: INFO: Normal operation', 'LOG: WARNING: Low disk space']
    7⟨LevelLogger C⟩ERROR: Connection failedLOG: ERROR: Connection failed['LOG: INFO: Normal operation', 'LOG: WARNING: Low disk space'] ['LOG: INFO: Normal operation', 'LOG: WARNING: Low disk space', 'LOG: ERROR: Connection failed']
    8⟨CountingLogger D⟩#1 First messageLOG: #1 First message[] ['LOG: #1 First message']
    9⟨CountingLogger D⟩#2 Second messageLOG: #2 Second message['LOG: #1 First message'] ['LOG: #1 First message', 'LOG: #2 Second message']
    ⋯ 2 more passes ⋯
    12⟨FullFeaturedLogger E⟩[22:54:22] [APP] #2 DEBUG: ProcessingLOG: [22:54:22] [APP] #2 DEBUG: Processing['LOG: [22:54:22] [APP] #1 INFO: Started'] ['LOG: [22:54:22] [APP] #1 INFO: Started', 'LOG: [22:54:22] [APP] #2 DEBUG: Processing']
    13⟨FullFeaturedLogger E⟩[22:54:22] [APP] #3 ERROR: Something wrongLOG: [22:54:22] [APP] #3 ERROR: Something wrong['LOG: [22:54:22] [APP] #1 INFO: Started', 'LOG: [22:54:22] [APP] #2 DEBUG: Processing'] ['LOG: [22:54:22] [APP] #1 INFO: Started', 'LOG: [22:54:22] [APP] #2 DEBUG: Processing', 'LOG: [22:54:22] [APP] #3 ERROR: Something wrong']
  5. print(basic.log("Hello"))

    87basic = Logger() #?createbasic88print(basic⟨Logger A⟩.log("Hello"))89print(basic⟨Logger A⟩.log("World"))90print(f"Total logs: {len(basic.logs)}")
    outputLOG: Hello
  6. print(basic.log("World"))

    88print(basic.log("Hello"))89print(basic⟨Logger A⟩.log("World"))90print(f"Total logs: {len(basic.logs['LOG: Hello', 'LOG: World'])}")9192print("\n--- Timestamp Logger ---")93ts_logger = TimestampLogger() #?createts94print(ts_logger.log("Application started")) #?tslog
    outputLOG: World
    Total logs: 2
    
    --- Timestamp Logger ---
  7. ts_logger ← ⟨TimestampLogger B⟩

    92print("\n--- Timestamp Logger ---")93ts_logger→ ⟨TimestampLogger B⟩ = TimestampLogger() #?createts94print(ts_logger⟨TimestampLogger B⟩.log("Application started")) #?tslog95print(ts_logger.log("Processing data"))
  8. timestamp ← 22:54:22, timestamped_message ← [22:54:22] Application started

    pass 1 of 2
    19def log(self⟨TimestampLogger B⟩, messageApplication started): #?timestamplog20    # Add timestamp, then call parent #?addtimestamp21    from datetime import datetime22    timestamp→ 22:54:22 = datetime<class 'datetime.datetime'>.now().strftime("%H:%M:%S") #?gettimestamp23    timestamped_message→ [22:54:22] Application started = f"[{timestamp22:54:22}] {messageApplication started}" #?formatmsg24    25    # Call parent's log with enhanced message #?callparent26    return super().log(timestamped_message[22:54:22] Application started) #?superlog
  9. print(ts_logger.log("Application started")) #?tslog

    93ts_logger = TimestampLogger() #?createts94print(ts_logger⟨TimestampLogger B⟩.log("Application started")) #?tslog95print(ts_logger⟨TimestampLogger B⟩.log("Processing data"))96print(f"Logs stored: {ts_logger.logs}")
    outputLOG: [22:54:22] Application started
  10. timestamp ← 22:54:22, timestamped_message ← [22:54:22] Processing data

    pass 2 of 2
    19def log(self⟨TimestampLogger B⟩, messageProcessing data): #?timestamplog20    # Add timestamp, then call parent #?addtimestamp21    from datetime import datetime22    timestamp→ 22:54:22 = datetime<class 'datetime.datetime'>.now().strftime("%H:%M:%S") #?gettimestamp23    timestamped_message→ [22:54:22] Processing data = f"[{timestamp22:54:22}] {messageProcessing data}" #?formatmsg24    25    # Call parent's log with enhanced message #?callparent26    return super().log(timestamped_message[22:54:22] Processing data) #?superlog
  11. print(ts_logger.log("Processing data"))

    94print(ts_logger.log("Application started")) #?tslog95print(ts_logger⟨TimestampLogger B⟩.log("Processing data"))96print(f"Logs stored: {ts_logger.logs['LOG: [22:54:22] Application started', 'LOG: [22:54:22] Processing data']}")9798print("\n--- Level Logger ---")99level_logger = LevelLogger() #?createlevel100print(level_logger.info("Normal operation")) #?levelinfo
    outputLOG: [22:54:22] Processing data
    Logs stored: ['LOG: [22:54:22] Application started', 'LOG: [22:54:22] Processing data']
    
    --- Level Logger ---
  12. level_logger ← ⟨LevelLogger C⟩

    98print("\n--- Level Logger ---")99level_logger→ ⟨LevelLogger C⟩ = LevelLogger() #?createlevel100print(level_logger⟨LevelLogger C⟩.info("Normal operation")) #?levelinfo101print(level_logger.warning("Low disk space")) #?levelwarn
  13. def info(self, message): #?infomethod

    37# Convenience methods #?convenience38def info(self⟨LevelLogger C⟩, messageNormal operation): #?infomethod39    return self.log(messageNormal operation, "INFO")
  14. leveled_message ← INFO: Normal operation

    pass 1 of 3
    32def log(self⟨LevelLogger C⟩, messageNormal operation, levelINFO="INFO"): #?levellog33    # Add level prefix, then call parent #?addlevel34    leveled_message→ INFO: Normal operation = f"{levelINFO}: {messageNormal operation}" #?levelmsg35    return super().log(leveled_messageINFO: Normal operation)
    All 3 passes — pass 1 is the card above
    passmessagelevelleveled_message
    1Normal operationINFOINFO: Normal operation
    2Low disk spaceWARNINGWARNING: Low disk space
    3Connection failedERRORERROR: Connection failed
  15. print(level_logger.info("Normal operation")) #?levelinfo

    99level_logger = LevelLogger() #?createlevel100print(level_logger⟨LevelLogger C⟩.info("Normal operation")) #?levelinfo101print(level_logger⟨LevelLogger C⟩.warning("Low disk space")) #?levelwarn102print(level_logger.error("Connection failed")) #?levelerror
    outputLOG: INFO: Normal operation
  16. def warning(self, message): #?warningmethod

    41def warning(self⟨LevelLogger C⟩, messageLow disk space): #?warningmethod42    return self.log(messageLow disk space, "WARNING")
  17. print(level_logger.warning("Low disk space")) #?levelwarn

    100print(level_logger.info("Normal operation")) #?levelinfo101print(level_logger⟨LevelLogger C⟩.warning("Low disk space")) #?levelwarn102print(level_logger⟨LevelLogger C⟩.error("Connection failed")) #?levelerror
    outputLOG: WARNING: Low disk space
  18. def error(self, message): #?errormethod

    44def error(self⟨LevelLogger C⟩, messageConnection failed): #?errormethod45    return self.log(messageConnection failed, "ERROR")
  19. print(level_logger.error("Connection failed")) #?levelerror

    101print(level_logger.warning("Low disk space")) #?levelwarn102print(level_logger⟨LevelLogger C⟩.error("Connection failed")) #?levelerror103104print("\n--- Counting Logger ---")105count_logger = CountingLogger() #?createcount106print(count_logger.log("First message")) #?countlog
    outputLOG: ERROR: Connection failed
    
    --- Counting Logger ---
  20. def __init__(self): #?countinginit

    51def __init__(self⟨CountingLogger D⟩): #?countinginit52    super().__init__() #?superinit53    self.count = 0 #?initcount
  21. self.count ← 0

    52super().__init__() #?superinit53self.count→ 0 = 0 #?initcount
  22. count_logger ← ⟨CountingLogger D⟩

    104print("\n--- Counting Logger ---")105count_logger→ ⟨CountingLogger D⟩ = CountingLogger() #?createcount106print(count_logger⟨CountingLogger D⟩.log("First message")) #?countlog107print(count_logger.log("Second message"))
  23. self.count ← 1, numbered_message ← #1 First message

    pass 1 of 3
    55def log(self⟨CountingLogger D⟩, messageFirst message): #?countinglog56    self.count→ 1 += 1 #?increment57    # Extend message with count #?extendcount58    numbered_message→ #1 First message = f"#{self.count1} {messageFirst message}"59    return super().log(numbered_message#1 First message)
    All 3 passes — pass 1 is the card above
    passmessageself.countnumbered_message
    1First message0 1#1 First message
    2Second message1 2#2 Second message
    3Third message2 3#3 Third message
  24. print(count_logger.log("First message")) #?countlog

    105count_logger = CountingLogger() #?createcount106print(count_logger⟨CountingLogger D⟩.log("First message")) #?countlog107print(count_logger⟨CountingLogger D⟩.log("Second message"))108print(count_logger.log("Third message"))
    outputLOG: #1 First message
  25. print(count_logger.log("Second message"))

    106print(count_logger.log("First message")) #?countlog107print(count_logger⟨CountingLogger D⟩.log("Second message"))108print(count_logger⟨CountingLogger D⟩.log("Third message"))109print(f"Total count: {count_logger.count}")
    outputLOG: #2 Second message
  26. print(count_logger.log("Third message"))

    107print(count_logger.log("Second message"))108print(count_logger⟨CountingLogger D⟩.log("Third message"))109print(f"Total count: {count_logger.count3}")110111print("\n--- Full Featured Logger ---")112app_logger = FullFeaturedLogger("APP") #?createfull113print(app_logger.log("Started", "INFO"))
    outputLOG: #3 Third message
    Total count: 3
    
    --- Full Featured Logger ---
  27. def __init__(self, name): #?fullinit

    65def __init__(self⟨FullFeaturedLogger E⟩, nameAPP): #?fullinit66    super().__init__()67    self.name = name
  28. self.name ← APP, self.count ← 0

    66super().__init__()67self.name→ APP = nameAPP68self.count→ 0 = 0
  29. app_logger ← ⟨FullFeaturedLogger E⟩

    111print("\n--- Full Featured Logger ---")112app_logger→ ⟨FullFeaturedLogger E⟩ = FullFeaturedLogger("APP") #?createfull113print(app_logger⟨FullFeaturedLogger E⟩.log("Started", "INFO"))114print(app_logger.log("Processing", "DEBUG"))
  30. self.count ← 1, timestamp ← 22:54:22, formatted ← [22:54:22] [APP] #1 INFO: Started

    pass 1 of 3
    70def log(self⟨FullFeaturedLogger E⟩, messageStarted, levelINFO="INFO"): #?fulllog71    from datetime import datetime72    self.count→ 1 += 173    74    # Build full formatted message #?buildmsg75    timestamp→ 22:54:22 = datetime<class 'datetime.datetime'>.now().strftime("%H:%M:%S")76    formatted→ [22:54:22] [APP] #1 INFO: Started = f"[{timestamp22:54:22}] [{self.nameAPP}] #{self.count1} {levelINFO}: {messageStarted}"77    78    # Still use parent's storage mechanism #?useparent79    return super().log(formatted[22:54:22] [APP] #1 INFO: Started)
    All 3 passes — pass 1 is the card above
    passmessagelevelself.counttimestampformatted
    1StartedINFO0 122:54:22[22:54:22] [APP] #1 INFO: Started
    2ProcessingDEBUG1 222:54:22[22:54:22] [APP] #2 DEBUG: Processing
    3Something wrongERROR2 322:54:22[22:54:22] [APP] #3 ERROR: Something wrong
  31. print(app_logger.log("Started", "INFO"))

    112app_logger = FullFeaturedLogger("APP") #?createfull113print(app_logger⟨FullFeaturedLogger E⟩.log("Started", "INFO"))114print(app_logger⟨FullFeaturedLogger E⟩.log("Processing", "DEBUG"))115print(app_logger.log("Something wrong", "ERROR"))
    outputLOG: [22:54:22] [APP] #1 INFO: Started
  32. print(app_logger.log("Processing", "DEBUG"))

    113print(app_logger.log("Started", "INFO"))114print(app_logger⟨FullFeaturedLogger E⟩.log("Processing", "DEBUG"))115print(app_logger⟨FullFeaturedLogger E⟩.log("Something wrong", "ERROR"))
    outputLOG: [22:54:22] [APP] #2 DEBUG: Processing
  33. print(app_logger.log("Something wrong", "ERROR"))

    114print(app_logger.log("Processing", "DEBUG"))115print(app_logger⟨FullFeaturedLogger E⟩.log("Something wrong", "ERROR"))116117print("\n=== Extend vs Override ===")118print("""119OVERRIDE: Replace parent's behavior completely120    def method(self):121        # all new code122123EXTEND: Add to parent's behavior124    def method(self):125        # do something extra126        super().method()  # then call parent127        # optionally do more128129Benefits of extending:1301. Reuse parent's logic1312. Add functionality without duplicating code1323. Parent changes automatically apply1334. Can pre-process OR post-process134""")
    outputLOG: [22:54:22] [APP] #3 ERROR: Something wrong
    
    === Extend vs Override ===
    
    OVERRIDE: Replace parent's behavior completely
        def method(self):
            # all new code
    
    EXTEND: Add to parent's behavior
        def method(self):
            # do something extra
            super().method()  # then call parent
            # optionally do more
    
    Benefits of extending:
    1. Reuse parent's logic
    2. Add functionality without duplicating code
    3. Parent changes automatically apply
    4. Can pre-process OR post-process

Call super().method() then add more logic. Best of both worlds.

Type checking with isinstance

Check inheritance relationships at runtime.

isinstance_issubclass.py
Replay: real traced execution (multi-file project)
# isinstance() and issubclass()

class Vehicle:
    """Base class for all vehicles."""
    def __init__(self, brand):
        self.brand = brand

    def start(self):
        return f"{self.brand} starting..."


class Car(Vehicle):
    """Car extends Vehicle."""
    def __init__(self, brand, num_doors):
        super().__init__(brand)
        self.num_doors = num_doors

    def drive(self):
        return f"{self.brand} car driving"


class ElectricCar(Car):
    """ElectricCar extends Car."""
    def __init__(self, brand, num_doors, battery_capacity):
        super().__init__(brand, num_doors)
        self.battery_capacity = battery_capacity

    def charge(self):
        return f"Charging {self.brand}"


class Motorcycle(Vehicle):
    """Motorcycle extends Vehicle."""
    def __init__(self, brand, cc):
        super().__init__(brand)
        self.cc = cc

    def wheelie(self):
        return f"{self.brand} doing a wheelie!"


print("=== isinstance() and issubclass() ===\n")

# Create instances
my_car = Car("Toyota", 4)
my_tesla = ElectricCar("Tesla", 4, 100)
my_bike = Motorcycle("Harley", 1200)

print("--- isinstance() checks ---")

# isinstance(object, class)
print(f"my_car is Car: {isinstance(my_car, Car)}")
print(f"my_car is Vehicle: {isinstance(my_car, Vehicle)}")
print(f"my_car is ElectricCar: {isinstance(my_car, ElectricCar)}")

print()

# Tesla checks
print(f"my_tesla is ElectricCar: {isinstance(my_tesla, ElectricCar)}")
print(f"my_tesla is Car: {isinstance(my_tesla, Car)}")
print(f"my_tesla is Vehicle: {isinstance(my_tesla, Vehicle)}")
print(f"my_tesla is Motorcycle: {isinstance(my_tesla, Motorcycle)}")

print()

# Check multiple types
print(f"my_car is (Car, Motorcycle): {isinstance(my_car, (Car, Motorcycle))}")
print(f"my_bike is (Car, Motorcycle): {isinstance(my_bike, (Car, Motorcycle))}")

print("\n--- issubclass() checks ---")

# issubclass(class, class)
print(f"Car is subclass of Vehicle: {issubclass(Car, Vehicle)}")
print(f"ElectricCar is subclass of Car: {issubclass(ElectricCar, Car)}")
print(f"ElectricCar is subclass of Vehicle: {issubclass(ElectricCar, Vehicle)}")
print(f"Car is subclass of Car: {issubclass(Car, Car)}")
print(f"Car is subclass of Motorcycle: {issubclass(Car, Motorcycle)}")

print("\n--- Practical use: Processing different types ---")

# List of mixed vehicles
vehicles = [my_car, my_tesla, my_bike]

for v in vehicles:
    print(f"\n{v.brand}:")
    print(f"  {v.start()}")

    # Type-specific behavior
    if isinstance(v, ElectricCar):
        print(f"  {v.charge()}")

    if isinstance(v, Car):
        print(f"  {v.drive()}")

    if isinstance(v, Motorcycle):
        print(f"  {v.wheelie()}")

print("\n--- Type hierarchy ---")
print(f"ElectricCar MRO: {ElectricCar.__mro__}")

print("\n=== isinstance vs type() ===")
print("""
isinstance(obj, cls):
  - Returns True if obj is instance of cls OR subclass
  - Preferred for inheritance hierarchies
  - Can check multiple types with tuple

type(obj) == cls:
  - Returns True only for exact type match
  - Does NOT consider inheritance
  - Use when exact type matters

Example:
  isinstance(my_tesla, Car)  # True (Tesla IS-A Car)
  type(my_tesla) == Car      # False (exact type is ElectricCar)
""")

# Demonstrate difference
print("--- isinstance vs type ===")
print(f"isinstance(my_tesla, Car): {isinstance(my_tesla, Car)}")
print(f"type(my_tesla) == Car: {type(my_tesla) == Car}")
print(f"type(my_tesla) == ElectricCar: {type(my_tesla) == ElectricCar}")

  1. """Base class for all vehicles."""

    3class Vehicle: #?vehicleclass4    """Base class for all vehicles."""5    def __init__(self, brand):6        self.brand = brand7    8    def start(self):9        return f"{self.brand} starting..."101112class Car(Vehicle): #?carclass13    """Car extends Vehicle."""14    def __init__(self, brand, num_doors):15        super().__init__(brand)16        self.num_doors = num_doors17    18    def drive(self):19        return f"{self.brand} car driving"202122class ElectricCar(Car): #?electricclass23    """ElectricCar extends Car."""24    def __init__(self, brand, num_doors, battery_capacity):25        super().__init__(brand, num_doors)26        self.battery_capacity = battery_capacity27    28    def charge(self):29        return f"Charging {self.brand}"303132class Motorcycle(Vehicle): #?motorcycleclass33    """Motorcycle extends Vehicle."""34    def __init__(self, brand, cc):35        super().__init__(brand)36        self.cc = cc37    38    def wheelie(self):39        return f"{self.brand} doing a wheelie!"404142print("=== isinstance() and issubclass() ===\n")4344# Create instances #?createvehicles45my_car = Car("Toyota", 4) #?carinstance46my_tesla = ElectricCar("Tesla", 4, 100) #?teslainstance
    output=== isinstance() and issubclass() ===
  2. def __init__(self, brand, num_doors):

    pass 1 of 2
    13"""Car extends Vehicle."""14def __init__(self⟨Car A⟩, brandToyota, num_doors4):15    super().__init__(brand)16    self.num_doors = num_doors
  3. self.brand ← Toyota

    pass 1 of 3
    4"""Base class for all vehicles."""5def __init__(self⟨Car A⟩, brandToyota):6    self.brand→ Toyota = brandToyota
    All 3 passes — pass 1 is the card above
    passselfbrandself.brand
    1⟨Car A⟩ToyotaToyota
    2⟨ElectricCar B⟩TeslaTesla
    3⟨Motorcycle C⟩HarleyHarley
  4. self.num_doors ← 4

    15super().__init__(brand)16self.num_doors→ 4 = num_doors4
  5. my_car ← ⟨Car A⟩

    44# Create instances #?createvehicles45my_car→ ⟨Car A⟩ = Car("Toyota", 4) #?carinstance46my_tesla = ElectricCar("Tesla", 4, 100) #?teslainstance47my_bike = Motorcycle("Harley", 1200) #?bikeinstance
  6. def __init__(self, brand, num_doors, battery_capacity):

    23"""ElectricCar extends Car."""24def __init__(self⟨ElectricCar B⟩, brandTesla, num_doors4, battery_capacity100):25    super().__init__(brand, num_doors)26    self.battery_capacity = battery_capacity
  7. def __init__(self, brand, num_doors):

    pass 2 of 2
    13"""Car extends Vehicle."""14def __init__(self⟨ElectricCar B⟩, brandTesla, num_doors4):15    super().__init__(brand)16    self.num_doors = num_doors
  8. self.num_doors ← 4

    15super().__init__(brand)16self.num_doors→ 4 = num_doors4
  9. self.battery_capacity ← 100

    25super().__init__(brand, num_doors)26self.battery_capacity→ 100 = battery_capacity100
  10. my_tesla ← ⟨ElectricCar B⟩

    45my_car = Car("Toyota", 4) #?carinstance46my_tesla→ ⟨ElectricCar B⟩ = ElectricCar("Tesla", 4, 100) #?teslainstance47my_bike = Motorcycle("Harley", 1200) #?bikeinstance
  11. def __init__(self, brand, cc):

    33"""Motorcycle extends Vehicle."""34def __init__(self⟨Motorcycle C⟩, brandHarley, cc1200):35    super().__init__(brand)36    self.cc = cc
  12. self.cc ← 1200

    35super().__init__(brand)36self.cc→ 1200 = cc1200
  13. my_bike ← ⟨Motorcycle C⟩, vehicles ← [⟨Car A⟩, ⟨ElectricCar B⟩, ⟨Motorcycle C⟩]

    46my_tesla = ElectricCar("Tesla", 4, 100) #?teslainstance47my_bike→ ⟨Motorcycle C⟩ = Motorcycle("Harley", 1200) #?bikeinstance4849print("--- isinstance() checks ---")5051# isinstance(object, class) #?isinstancedemo52print(f"my_car is Car: {isinstance(my_car⟨Car A⟩, Car<class '__main__.Car'>)}") #?cariscar53print(f"my_car is Vehicle: {isinstance(my_car⟨Car A⟩, Vehicle<class '__main__.Vehicle'>)}") #?carisvehicle54print(f"my_car is ElectricCar: {isinstance(my_car⟨Car A⟩, ElectricCar<class '__main__.ElectricCar'>)}") #?cariselectric5556print()5758# Tesla checks #?teslacheck59print(f"my_tesla is ElectricCar: {isinstance(my_tesla⟨ElectricCar B⟩, ElectricCar<class '__main__.ElectricCar'>)}") #?teslaiselectric60print(f"my_tesla is Car: {isinstance(my_tesla⟨ElectricCar B⟩, Car<class '__main__.Car'>)}") #?teslaiscar61print(f"my_tesla is Vehicle: {isinstance(my_tesla⟨ElectricCar B⟩, Vehicle<class '__main__.Vehicle'>)}") #?teslaisvehicle62print(f"my_tesla is Motorcycle: {isinstance(my_tesla⟨ElectricCar B⟩, Motorcycle<class '__main__.Motorcycle'>)}") #?teslaisbike6364print()6566# Check multiple types #?multipletypes67print(f"my_car is (Car, Motorcycle): {isinstance(my_car⟨Car A⟩, (Car<class '__main__.Car'>, Motorcycle<class '__main__.Motorcycle'>))}") #?caristuple68print(f"my_bike is (Car, Motorcycle): {isinstance(my_bike⟨Motorcycle C⟩, (Car<class '__main__.Car'>, Motorcycle<class '__main__.Motorcycle'>))}") #?bikeistuple6970print("\n--- issubclass() checks ---")7172# issubclass(class, class) #?issubclassdemo73print(f"Car is subclass of Vehicle: {issubclass(Car<class '__main__.Car'>, Vehicle<class '__main__.Vehicle'>)}") #?carsubvehicle74print(f"ElectricCar is subclass of Car: {issubclass(ElectricCar<class '__main__.ElectricCar'>, Car<class '__main__.Car'>)}") #?electricsubcar75print(f"ElectricCar is subclass of Vehicle: {issubclass(ElectricCar<class '__main__.ElectricCar'>, Vehicle<class '__main__.Vehicle'>)}") #?electricsubvehicle76print(f"Car is subclass of Car: {issubclass(Car<class '__main__.Car'>, Car)}") #?carsubcar77print(f"Car is subclass of Motorcycle: {issubclass(Car<class '__main__.Car'>, Motorcycle<class '__main__.Motorcycle'>)}") #?carsubmotorcycle7879print("\n--- Practical use: Processing different types ---")8081# List of mixed vehicles #?mixedlist82vehicles→ [⟨Car A⟩, ⟨ElectricCar B⟩, ⟨Motorcycle C⟩] = [my_car⟨Car A⟩, my_tesla⟨ElectricCar B⟩, my_bike⟨Motorcycle C⟩] #?vehiclelist
    output--- isinstance() checks ---
    my_car is Car: True
    my_car is Vehicle: True
    my_car is ElectricCar: False
    my_tesla is ElectricCar: True
    my_tesla is Car: True
    my_tesla is Vehicle: True
    my_tesla is Motorcycle: False
    my_car is (Car, Motorcycle): True
    my_bike is (Car, Motorcycle): True
    
    --- issubclass() checks ---
    Car is subclass of Vehicle: True
    ElectricCar is subclass of Car: True
    ElectricCar is subclass of Vehicle: True
    Car is subclass of Car: True
    Car is subclass of Motorcycle: False
    
    --- Practical use: Processing different types ---
  14. for v in vehicles: #?iteratevehicles

    pass 1 of 3
    84for v⟨Car A⟩ in vehicles[⟨Car A⟩, ⟨ElectricCar B⟩, ⟨Motorcycle C⟩]: #?iteratevehicles85    print(f"\n{v.brandToyota}:")86    print(f"  {v⟨Car A⟩.start()}") #?callstart
    output
    Toyota:
    All 3 passes — pass 1 is the card above
    passvv.brand
    1⟨Car A⟩Toyota
    2⟨ElectricCar B⟩Tesla
    3⟨Motorcycle C⟩Harley
  15. def start(self):

    pass 1 of 3
    8def start(self⟨Car A⟩):9    return f"{self.brandToyota} starting..."
    All 3 passes — pass 1 is the card above
    passselfself.brand
    1⟨Car A⟩Toyota
    2⟨ElectricCar B⟩Tesla
    3⟨Motorcycle C⟩Harley
  16. print(f" {v.start()}") #?callstart

    85print(f"\n{v.brand}:")86print(f"  {v⟨Car A⟩.start()}") #?callstart
    output  Toyota starting...
  17. if isinstance(v, Car): #?checkcar

    pass 1 of 2
    92if isinstance(v⟨Car A⟩, Car<class '__main__.Car'>): #?checkcar93    print(f"  {v⟨Car A⟩.drive()}") #?calldrive
  18. def drive(self):

    pass 1 of 2
    18def drive(self⟨Car A⟩):19    return f"{self.brandToyota} car driving"
  19. print(f" {v.drive()}") #?calldrive

    92if isinstance(v, Car): #?checkcar93    print(f"  {v⟨Car A⟩.drive()}") #?calldrive
    output  Toyota car driving
  20. print(f" {v.start()}") #?callstart

    85print(f"\n{v.brand}:")86print(f"  {v⟨ElectricCar B⟩.start()}") #?callstart
    output  Tesla starting...
  21. if isinstance(v, ElectricCar): #?checkelectric

    88# Type-specific behavior #?typespecific89if isinstance(v⟨ElectricCar B⟩, ElectricCar<class '__main__.ElectricCar'>): #?checkelectric90    print(f"  {v⟨ElectricCar B⟩.charge()}") #?callcharge
  22. def charge(self):

    28def charge(self⟨ElectricCar B⟩):29    return f"Charging {self.brandTesla}"
  23. print(f" {v.charge()}") #?callcharge

    89if isinstance(v, ElectricCar): #?checkelectric90    print(f"  {v⟨ElectricCar B⟩.charge()}") #?callcharge
    output  Charging Tesla
  24. if isinstance(v, Car): #?checkcar

    pass 2 of 2
    92if isinstance(v⟨ElectricCar B⟩, Car<class '__main__.Car'>): #?checkcar93    print(f"  {v⟨ElectricCar B⟩.drive()}") #?calldrive
  25. def drive(self):

    pass 2 of 2
    18def drive(self⟨ElectricCar B⟩):19    return f"{self.brandTesla} car driving"
  26. print(f" {v.drive()}") #?calldrive

    92if isinstance(v, Car): #?checkcar93    print(f"  {v⟨ElectricCar B⟩.drive()}") #?calldrive
    output  Tesla car driving
  27. print(f" {v.start()}") #?callstart

    85print(f"\n{v.brand}:")86print(f"  {v⟨Motorcycle C⟩.start()}") #?callstart
    output  Harley starting...
  28. if isinstance(v, Motorcycle): #?checkmotorcycle

    95if isinstance(v⟨Motorcycle C⟩, Motorcycle<class '__main__.Motorcycle'>): #?checkmotorcycle96    print(f"  {v⟨Motorcycle C⟩.wheelie()}") #?callwheelie
  29. def wheelie(self):

    38def wheelie(self⟨Motorcycle C⟩):39    return f"{self.brandHarley} doing a wheelie!"
  30. print(f" {v.wheelie()}") #?callwheelie

    95if isinstance(v, Motorcycle): #?checkmotorcycle96    print(f"  {v⟨Motorcycle C⟩.wheelie()}") #?callwheelie
    output  Harley doing a wheelie!
  31. print(f"ElectricCar MRO: {ElectricCar.__mro__}") #?mro

    98print("\n--- Type hierarchy ---")99print(f"ElectricCar MRO: {ElectricCar.__mro__(<class '__main__.ElectricCar'>, <class '__main__.Car'>, <class '__main__.Vehicle'>, <class 'object'>)}") #?mro100101print("\n=== isinstance vs type() ===")102print("""103isinstance(obj, cls):104  - Returns True if obj is instance of cls OR subclass105  - Preferred for inheritance hierarchies106  - Can check multiple types with tuple107108type(obj) == cls:109  - Returns True only for exact type match110  - Does NOT consider inheritance111  - Use when exact type matters112113Example:114  isinstance(my_tesla, Car)  # True (Tesla IS-A Car)115  type(my_tesla) == Car      # False (exact type is ElectricCar)116""")117118# Demonstrate difference #?typedemo119print("--- isinstance vs type ===")120print(f"isinstance(my_tesla, Car): {isinstance(my_tesla⟨ElectricCar B⟩, Car<class '__main__.Car'>)}") #?isinstancetesla121print(f"type(my_tesla) == Car: {type(my_tesla⟨ElectricCar B⟩) == Car<class '__main__.Car'>}") #?typetesla122print(f"type(my_tesla) == ElectricCar: {type(my_tesla⟨ElectricCar B⟩) == ElectricCar<class '__main__.ElectricCar'>}") #?typeexact
    output
    --- Type hierarchy ---
    ElectricCar MRO: (<class '__main__.ElectricCar'>, <class '__main__.Car'>, <class '__main__.Vehicle'>, <class 'object'>)
    
    === isinstance vs type() ===
    
    isinstance(obj, cls):
      - Returns True if obj is instance of cls OR subclass
      - Preferred for inheritance hierarchies
      - Can check multiple types with tuple
    
    type(obj) == cls:
      - Returns True only for exact type match
      - Does NOT consider inheritance
      - Use when exact type matters
    
    Example:
      isinstance(my_tesla, Car)  # True (Tesla IS-A Car)
      type(my_tesla) == Car      # False (exact type is ElectricCar)
    --- isinstance vs type ===
    isinstance(my_tesla, Car): True
    type(my_tesla) == Car: False
    type(my_tesla) == ElectricCar: True

isinstance(obj, Class) checks if obj is instance of Class or subclass.

isinstance Check type: `isinstance(x, Parent)`. Returns True for child classes too.

Exercise: practical.py

Build an employee hierarchy with inheritance