Suppose we have a Warrior class and a Mage class. Both have:
name, health attributesattack(), __str__() methodsWhat’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.
Character
name, health
attack()
__str__()
/ | \
Warrior Mage Healer
strength mana heal_power
Warrior, Mage, Healer inherit from CharacterHealer is-a Character”Character is the base class (or parent class)Work through the Extending Classes POGIL handout in your groups.
(after POGIL debrief)
Key points:
class Warrior(Character): — Warrior inherits from Charactersuper().__init__(...) — calls the parent constructorattack() in Warrior overrides the one in CharacterHealer 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.
What does each line print?
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 checksA subclass instance is also an instance of its parent class.
Make Mage inherit from Character:
Call the parent constructor from Mage.__init__:
True or False: A Mage object is also a Character object.
class Mage(Character):super().__init__(name, health)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.
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 basicsWithout handling:
With handling:
The except block runs only if an exception of that type is raised inside try.
Or catch multiple at once:
as e gives you access to the exception message.
else and finallyelse — runs when no exception occurredfinally — runs always (great for closing files, etc.)This program crashes when the user types a non-number. Add try/except to handle it gracefully:
Make it print a helpful message and ask again if the input is invalid.
raise: enforcing invariantsClasses can raise exceptions to reject bad values:
Compare to assert: similar idea, but raise is for production code; assert is for debugging.
raise vs ifUse raise (exceptions) for exceptional situations — things that shouldn’t happen if the caller uses your code correctly:
Use if for normal control flow — things you expect to happen:
Rule of thumb: if it’s a programming mistake or invalid state, raise. If it’s a normal user choice or condition, if.
Add a raise ValueError to this class constructor so it rejects invalid inputs:
Then test it: