class SavingsAccount(Account): makes a subclass: it has everything Account has, plus what you add. A method defined again in the subclass overrides the parent's version.
If the subclass has its own __init__, call the parent's with super().__init__(...), or the attributes the parent sets up won't exist.
Polymorphism means different classes answer the same method call in their own way. A list can hold standard and express parcels, and p.cost() does the right thing for each, with no if on the type.
isinstance(obj, Account) is True for subclasses too. When you check types in order, test the most specific class first.
Your own exceptions are classes too: class InvalidAmount(ValueError): pass. Code that catches ValueError still catches it, and code that cares can catch exactly InvalidAmount.
class Account:
def __init__(self, owner, balance=0.0):
self.owner = owner
self.balance = balance
class Savings(Account):
def __init__(self, owner, balance=0.0, rate=0.02):
super().__init__(owner, balance)
self.rate = rate
s = Savings("Mei", 1000.0)
s.owner, s.rate, isinstance(s, Account)
Out: ('Mei', 0.02, True). super().init sets owner and balance; Savings adds the rate.