Classes and objects
A class bundles data together with the functions that work on it. Most CS courses build on this idea, called object-oriented programming, so it is worth getting comfortable with early.
Your first class
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("insufficient funds")
self.balance -= amount
acct = BankAccount("Ada")
acct.deposit(50)
acct.withdraw(20)
print(acct.balance) # 30
A class is a blueprint; acct is an object (or instance) built from it. You can make as many objects as you like, and each has its own data.
__init__runs automatically when you create an object. It sets up the object's starting data.selfis the object the method was called on.acct.deposit(50)runsdepositwithselfset toacct.self.balanceis an attribute: a variable that belongs to one object.- Functions defined inside a class are called methods.
A readable printout: __repr__
By default, printing an object shows something unhelpful like <__main__.BankAccount object at 0x...>. Define __repr__ to fix that:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
print(Point(3, 4)) # Point(3, 4)
Methods with double underscores on both sides are called special methods (or "dunder" methods). Python calls them for you at particular moments.
Equality: __eq__
Without help, two objects are equal only if they are the very same object:
Point(1, 2) == Point(1, 2) # False, until we define __eq__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)
Point(1, 2) == Point(1, 2) # True
Inheritance
A class can build on another, reusing its methods and adding or changing some:
class SavingsAccount(BankAccount):
def __init__(self, owner, balance=0, rate=0.02):
super().__init__(owner, balance)
self.rate = rate
def add_interest(self):
self.deposit(self.balance * self.rate)
s = SavingsAccount("Grace", 100)
s.add_interest()
print(s.balance) # 102.0
super().__init__ runs the parent class's setup, so you do not repeat it. SavingsAccount also gets deposit and withdraw for free. Use inheritance when the new class really is a kind of the old one; otherwise, store one object inside another.
Dataclasses
Classes that mostly hold data need a lot of repetitive code. The dataclass decorator writes __init__, __repr__ and __eq__ for you from type-annotated fields:
from dataclasses import dataclass
@dataclass
class Student:
name: str
year: int
gpa: float = 0.0
s = Student("Ada", 2, 3.9)
print(s) # Student(name='Ada', year=2, gpa=3.9)
s == Student("Ada", 2, 3.9) # True
Exercises
1. Rectangle
Write a Rectangle class with width and height, and methods area() and perimeter().
Show answer
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
r = Rectangle(3, 4)
r.area(), r.perimeter() # (12, 14)
2. A counter that remembers
Write a class Counter with increment(), reset() and a value attribute starting at 0. Then make two counters and show that they are independent.
Show answer
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
def reset(self):
self.value = 0
a = Counter()
b = Counter()
a.increment()
a.increment()
b.increment()
print(a.value, b.value) # 2 1
Each object has its own self.value. (The standard library also has a collections.Counter, which is a different thing.)
3. Overdraft
Write CheckingAccount, a subclass of BankAccount that allows the balance to go down to −100 but no further.
Show answer
class CheckingAccount(BankAccount):
LIMIT = -100
def withdraw(self, amount):
if self.balance - amount < self.LIMIT:
raise ValueError("overdraft limit reached")
self.balance -= amount
c = CheckingAccount("Linus", 20)
c.withdraw(100)
print(c.balance) # -80
Defining withdraw again in the subclass overrides the parent's version. LIMIT is a class attribute, shared by every checking account.