Classes and Objects
So far you've stored data in variables, lists and dictionaries, and written functions that work on that data. A class bundles the two together: it describes a kind of thing, the data it holds and the actions it can perform. Each actual thing made from a class is an object.
You've been using objects all along: every string, list and dictionary is an object, and "hello".upper() calls a method on a string object. Now you'll make your own.
Defining a class
A class is a blueprint. By convention, class names use CapitalizedWords:
class Dog:
pass
rex = Dog()
fido = Dog()
print(rex)
print(type(rex))
print(rex is fido) # two separate objectsCalling the class like a function, Dog(), creates a new object. That object is also called an instance of the class.
The __init__ method
The __init__ method runs automatically whenever a new object is created. It sets up the object's starting data, its attributes:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
rex = Dog("Rex", 3)
fido = Dog("Fido", 7)
print(rex.name, rex.age)
print(fido.name, fido.age)The arguments you pass to Dog(...) go to __init__. Inside the class, self.name = name stores the value on that particular object, so each dog keeps its own name and age.
What is self?
self is the object the method is working on. When you write rex.name, you read the name attribute of the rex object. Inside the class, that same object is called self.
Every method's first parameter is self, but you don't pass it yourself: Python fills it in automatically with the object before the dot.
Methods
A method is a function defined inside a class. Methods can read and change the object's attributes through self:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name} says Woof!")
def have_birthday(self):
self.age += 1
print(f"Happy birthday {self.name}, you're now {self.age}!")
rex = Dog("Rex", 3)
rex.bark()
rex.have_birthday()
print(rex.age)rex.bark() is really shorthand for Dog.bark(rex). That's how self gets its value.
Changing attributes
Attributes can be read and changed from outside the class too:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
rex = Dog("Rex", 3)
rex.age = 4
rex.nickname = "Rexy" # adds a brand-new attribute to this object only
print(rex.age, rex.nickname)It's usually better to change an object through its methods, which can check that the new value makes sense.
A practical example: a bank account
Classes shine when data and behaviour belong together:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Insufficient funds")
self.balance -= amount
def describe(self):
return f"{self.owner}: {self.balance:.2f}"
account = BankAccount("Ada", 100)
account.deposit(50)
account.withdraw(30)
print(account.describe())
try:
account.withdraw(500)
except ValueError as e:
print("Error:", e)The account looks after its own rules: you can't deposit a negative amount or overdraw it.
Going deeper: "Private" attributes with an underscore optional
There's a loophole: nothing stops code outside the class from writing account.balance = -1000 directly and skipping the rules. Python has no truly private attributes. Instead, programmers agree on a convention: a name that starts with an underscore is internal, so don't touch it from outside:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance # internal: use the methods instead
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
def get_balance(self):
return self._balance
account = BankAccount("Ada", 100)
account.deposit(50)
print(account.get_balance())Python still lets you read account._balance, but the underscore tells other programmers "you're on your own if you change this". In the Decorators lesson you'll see @property, which lets account.balance be read like an attribute while the class stays in control.
Class attributes
Attributes set on self belong to one object. Attributes defined directly in the class body are shared by all objects of that class:
class Dog:
species = "Canis familiaris" # shared by every dog
count = 0
def __init__(self, name):
self.name = name # different for each dog
Dog.count += 1
a = Dog("Rex")
b = Dog("Fido")
print(a.species, b.species)
print(Dog.count, "dogs created")Objects in collections
Objects work with everything you've learned so far: lists, loops, sorting and more:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def passed(self):
return self.score >= 50
students = [Student("Mia", 82), Student("Leo", 45), Student("Ava", 91)]
for s in students:
status = "pass" if s.passed() else "fail"
print(f"{s.name}: {s.score} ({status})")
best = max(students, key=lambda s: s.score)
print("Top:", best.name)Exercises
Exercise 1: A Rectangle class
Create a Rectangle class with width and height attributes, and methods area() and perimeter(). The code below should print 12 and 14.
# define Rectangle here r = Rectangle(3, 4) print(r.area()) print(r.perimeter())
Store both values in __init__: self.width = width and self.height = height.
Each method reads the values through self, like return self.width * self.height.
Exercise 2: A counter
Create a Counter class that starts at 0, with increment(), reset() and a value attribute.
# define Counter here c = Counter() c.increment() c.increment() c.increment() print(c.value) # 3 c.reset() print(c.value) # 0
__init__ only needs self, and sets self.value = 0.
increment adds 1 to self.value, and reset sets it back to 0.
Exercise 3: Shopping cart
Create a Cart class that holds a list of (name, price) items, with add(name, price) and total() methods. Make sure each cart has its own list.
# define Cart here
cart = Cart()
cart.add("Book", 12.5)
cart.add("Pen", 1.25)
print(cart.total()) # 13.75
other = Cart()
print(other.total()) # 0Create the list in __init__ with self.items = [], so each cart gets its own list.
add appends a (name, price) tuple, and total adds up the prices: sum(price for name, price in self.items).
Exercise 4: Temperature
Create a Temperature class that stores degrees Celsius and has a to_fahrenheit() method. Then add a method is_freezing() that returns True at or below 0°C.
# define Temperature here t = Temperature(25) print(t.to_fahrenheit()) # 77.0 print(t.is_freezing()) # False print(Temperature(-3).is_freezing()) # True
Store the value in __init__ as self.celsius.
to_fahrenheit returns self.celsius * 9 / 5 + 32, and is_freezing returns self.celsius <= 0.
Summary
- A class is a blueprint, and an object (instance) is a thing made from it:
rex = Dog("Rex", 3). __init__runs when an object is created and sets its attributes onself.selfis the object a method is working on. Python passes it automatically.- Methods are functions inside a class that use and change the object's data.
- Attributes on
selfbelong to one object. Class attributes are shared by all objects.