Learn Python / Classes and Objects

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 objects
Output

Calling 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)
Output

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)
Output

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)
Output

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)
Output

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())
Output

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")
Output

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)
Output

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())
Output

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
Output

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())    # 0
Output

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
Output

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 on self.
  • self is 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 self belong to one object. Class attributes are shared by all objects.