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))
# two separate objects
print(rex is fido)Calling the class like a function, Dog(), creates a new object. That object is also called an instance of the class. Each call makes a different object, which is why rex is fido is False (is asks "are these the very same object?").
Printing an object shows something like <__main__.Dog object at 0x7f...>: the class name and where the object lives in memory. Not very friendly; the Special Methods lesson shows how to choose what gets printed.
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.
Here's what happens when Python runs rex = Dog("Rex", 3):
- Python creates a new, empty
Dogobject. - It calls
__init__, passing that new object asself, then"Rex"asnameand3asage. self.name = nameandself.age = agestore the values on the new object.- The finished object is given back and stored in
rex.
__init__ has two underscores on each side, and it never returns anything; Python hands back the object for you. In self.name = name, the name on the right is the parameter, and self.name on the left is the attribute stored on the object. Using the same word for both is normal.
Two common mistakes:
- Forgetting
self., as inname = name. That only creates a local variable, which disappears when__init__ends, sorex.namelater gives anAttributeError. - Forgetting
selfin thedefline, as indef __init__(name, age):. Python still passes the object first, so you get aTypeErrorabout the number of arguments.
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. In rex.bark(), self is rex; in fido.bark(), self is fido. One method, written once, works for every dog.
self is just a name, but it's such a strong convention that you should always use it.
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.
have_birthday changes self.age, and the change sticks: after the call, rex.age is 4. Methods can also take extra parameters after self, like deposit(self, amount) below, and return values just like ordinary functions.
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
# a new attribute, on rex only
rex.nickname = "Rexy"
print(rex.age, rex.nickname)Reading an attribute that doesn't exist, such as rex.colour, is an AttributeError. Attribute names are case-sensitive like everything else, so rex.Name won't find name.
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
# internal: use the methods instead
self._balance = balance
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:
# shared by every dog
species = "Canis familiaris"
count = 0
def __init__(self, name):
# different for each dog
self.name = name
Dog.count += 1
a = Dog("Rex")
b = Dog("Fido")
print(a.species, b.species)
print(Dog.count, "dogs created")| Instance attribute | Class attribute | |
|---|---|---|
| Created with | self.name = ..., usually in __init__ |
species = ... directly in the class body |
| Belongs to | one object | the class, shared by all its objects |
| Example | each dog's name |
every dog's species |
The counter uses Dog.count += 1, not self.count += 1. Assigning through self would create a separate count attribute on that one dog, and the shared counter would stay at 0.
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.
Know something worth teaching? Write your own tutorial with runnable examples and share it.