Inheritance and Exceptions

CS 108

Monday: Inheritance

Why Inheritance?

Suppose we have a Warrior class and a Mage class. Both have:

  • name, health attributes
  • attack(), __str__() methods

What’s the problem with maintaining these two separately?

Code smell: copying code means bugs in two places, updates in two places, inconsistencies creep in.

Inheritance lets us share common code in a base class and specialize in subclasses.

UML Class Diagram

        Character
      name, health
       attack()
       __str__()
    /      |      \
Warrior   Mage   Healer
strength  mana   heal_power
  • Warrior, Mage, Healer inherit from Character
  • “A Healer is-a Character
  • Character is the base class (or parent class)
  • The three subclasses are derived classes

POGIL: Extending Classes

Work through the Extending Classes POGIL handout in your groups.

  • Model 1: UML Class Diagrams (~15 min)
  • Model 2: Single-Class Approach (~10 min)
  • Model 3: Derived Classes (~20 min)

Inheritance: Syntax Summary

(after POGIL debrief)

Defining a Subclass

class Character:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def attack(self, target):
        print(f"{self.name} attacks {target.name}!")

    def __str__(self):
        return f"{self.name} (HP: {self.health})"
class Warrior(Character):
    def __init__(self, name, health, strength):
        super().__init__(name, health)
        self.strength = strength

    def attack(self, target):
        target.health -= self.strength
        print(f"{self.name} hits {target.name} for {self.strength} damage!")

Key points:

  • class Warrior(Character): — Warrior inherits from Character
  • super().__init__(...) — calls the parent constructor
  • attack() in Warrior overrides the one in Character

A Subclass Can Repurpose a Method

class Healer(Character):
    def __init__(self, name, health, heal_power):
        super().__init__(name, health)
        self.heal_power = heal_power

    def attack(self, target):
        target.health += self.heal_power
        print(f"{self.name} heals {target.name} for {self.heal_power} HP!")

Healer inherits from Character, but its attack() does the opposite of damage.

character.attack(target) works for all characters, but behavior can be very different.

This is polymorphism — the caller doesn’t need to know what kind of character it has.

Using Inherited Classes

aragorn = Warrior("Aragorn", health=100, strength=15)
mage = Mage("Gandalf", health=80, mana=50)
healer = Healer("Arwen", health=70, heal_power=20)

What does each line print?

print(aragorn)
aragorn.attack(mage)
healer.attack(mage)
print(mage)
Aragorn (HP: 100)
Aragorn hits Gandalf for 15 damage!
Arwen heals Gandalf for 20 HP!
Gandalf (HP: 85)

__str__() is inherited from Character; attack() is overridden in each subclass.

isinstance checks

aragorn = Warrior("Aragorn", health=100, strength=15)

isinstance(aragorn, Warrior)    # True
isinstance(aragorn, Character)  # True  ← aragorn IS-A Character
isinstance(aragorn, Healer)     # False

A subclass instance is also an instance of its parent class.

Fill in the Blank

  1. Make Mage inherit from Character:

    class Mage(___):
        ...
  2. Call the parent constructor from Mage.__init__:

    def __init__(self, name, health, mana):
        ___(name, health)
        self.mana = mana
  3. True or False: A Mage object is also a Character object.

  1. class Mage(Character):
  2. super().__init__(name, health)
  3. True

Wednesday: Exceptions

You’ve seen these before

All semester, these have crashed your programs:

ValueError: invalid literal for int() with base 10: 'hello'
TypeError: can only concatenate str (not "int") to str
IndexError: list index out of range
FileNotFoundError: [Errno 2] No such file or directory: 'data.csv'
KeyError: 'name'
ZeroDivisionError: division by zero

Today: learn to handle them gracefully and raise your own.

Match the Exception

Which exception type does each expression raise?

Expression Exception
int("hello") ?
[1, 2, 3][10] ?
"hi" + 4 ?
open("missing.txt") ?
{"a": 1}["b"] ?
Expression Exception
int("hello") ValueError
[1, 2, 3][10] IndexError
"hi" + 4 TypeError
open("missing.txt") FileNotFoundError
{"a": 1}["b"] KeyError

try / except: the basics

Without handling:

age = int(input("Enter your age: "))  # crashes if user types "twenty"

With handling:

try:
    age = int(input("Enter your age: "))
    print(f"In 10 years you'll be {age + 10}")
except ValueError:
    print("That's not a number!")

The except block runs only if an exception of that type is raised inside try.

Catching specific types

try:
    result = numerator / denominator
except ZeroDivisionError:
    print("Can't divide by zero!")
except TypeError:
    print("Both values must be numbers.")

Or catch multiple at once:

except (ValueError, TypeError) as e:
    print(f"Bad input: {e}")

as e gives you access to the exception message.

else and finally

try:
    f = open("data.txt")
except FileNotFoundError:
    print("File not found.")
else:
    print("File opened successfully!")   # runs only if no exception
    data = f.read()
finally:
    print("This always runs.")           # cleanup goes here
  • else — runs when no exception occurred
  • finally — runs always (great for closing files, etc.)

Exercise: Fix the crash

This program crashes when the user types a non-number. Add try/except to handle it gracefully:

def get_count():
    n = int(input("How many players? "))
    return n

count = get_count()
print(f"Starting game with {count} players.")

Make it print a helpful message and ask again if the input is invalid.

raise: enforcing invariants

Classes can raise exceptions to reject bad values:

class BankAccount:
    def __init__(self, balance):
        if balance < 0:
            raise ValueError("Balance cannot be negative.")
        self.balance = balance
acct = BankAccount(100)   # fine
acct = BankAccount(-50)   # raises ValueError

Compare to assert: similar idea, but raise is for production code; assert is for debugging.

raise vs if

Use raise (exceptions) for exceptional situations — things that shouldn’t happen if the caller uses your code correctly:

def set_player_count(n):
    if n <= 0:
        raise ValueError(f"Player count must be positive, got {n}")
    self.players = n

Use if for normal control flow — things you expect to happen:

if score > high_score:
    high_score = score

Rule of thumb: if it’s a programming mistake or invalid state, raise. If it’s a normal user choice or condition, if.

Exercise: Add validation

Add a raise ValueError to this class constructor so it rejects invalid inputs:

class Rectangle:
    def __init__(self, width, height):
        # TODO: raise ValueError if width or height is not positive
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

Then test it:

try:
    r = Rectangle(-3, 5)
except ValueError as e:
    print(f"Caught: {e}")

Friday: Wrap-Up

Course Evaluations

Be honest, be balanced. Include both things to keep and things to change.

I’ll step out in the hallway — come get me once everyone has finished.

Lab Review

CS 108 Day len(class_meetings)-1

  • Takeaways from Perspectives forums
  • Computing Beyond CS 108
  • Final projects

A Prayer

This is my prayer: that your love may abound more and more in knowledge and depth of insight, so that you may be able to discern what is best and may be pure and blameless for the day of Christ, filled with the fruit of righteousness that comes through Jesus Christ—to the glory and praise of God.

— Philippians 1:9–11

…and an Exhortation

Do nothing out of selfish ambition or vain conceit. Rather, in humility value others above yourselves, not looking to your own interests but each of you to the interests of the others.

— Philippians 2:3–4

(This is what we started with on Day 1.)

  • We practiced this in in-class exercises and pair programming in lab.

Takeaways from Perspectives

What is something you want to remember from our Forum discussions this semester?

Discuss with neighbors. Write a short phrase on the board.

Computing Beyond CS 108: Topics

How to build it? Many other languages:

  • HTML/CSS/JavaScript: websites (and more!)
  • Swift, Java: phone apps
  • SQL, R, Julia, MATLAB: data analysis, simulation, visualization
  • Rust, C++, C#, Clojure, …

Why does it work?

  • Theory of computation, programming language theory
  • Numerical methods, statistical learning, optimization, linear algebra
  • Distributed systems, databases

What impact does it have on people and society?

  • Fairness, Accountability, Transparency
  • Ethics, governance, policy

Computing Beyond CS 108: Courses

Data Science and Machine Learning

  • DATA 202: Data Wrangling and Machine Learning
  • CS 375/376: Machine Learning and AI
  • STAT 245: Applied Data Analysis
  • DATA 304/385: Visualization and Topics
  • STAT 341: Computational Bayesian Statistics

Mathematics and Statistics

  • MATH 251/252: Discrete Math, Theory of Computation
  • MATH 385: Optimization and other topics
  • MATH 255/355: Linear Algebra

Computer Science

  • CS 112, 212: Data Structures and Algorithms
  • CS 262: Software Engineering (app development)
  • Other electives: web development, networking, graphics, databases, IoT, …

Final Projects

  • Initial submission (working code + report) due before showcase
  • Final submission due end-of-day on showcase day

See you at the showcase!