Why classes exist

Once a program tracks several related values — a bank account's owner, balance and history, say — passing them around as loose variables becomes messy. A class bundles data (attributes) and the functions that operate on it (methods) into one blueprint. Each concrete thing built from the blueprint is an object or instance. You have used objects all along: "abc".upper() calls a method on a string object.

Defining a class

class Account:
    bank = "PyBank"                     # class attribute (shared)

    def __init__(self, owner, balance=0):
        self.owner = owner              # instance attributes
        self.balance = balance
        self.history = []

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self.balance += amount
        self.history.append(("deposit", amount))

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount
        self.history.append(("withdraw", amount))

acc = Account("Ada", 100)
acc.deposit(50)
acc.withdraw(30)
print(acc.owner, acc.balance, acc.history)
print(Account.bank, acc.bank)
Ada 120 [('deposit', 50), ('withdraw', 30)] PyBank PyBank
  • __init__ runs automatically when you create an instance. It is the constructor.
  • self is the instance the method was called on. Python passes it for you: acc.deposit(50) is really Account.deposit(acc, 50).
  • Class attributes are shared by all instances; instance attributes belong to one object.

__str__ and __repr__

Printing an object gives an unhelpful <__main__.Account object at 0x…> unless you define how it should look:

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __repr__(self):          # for developers / debugging
        return f"Point({self.x}, {self.y})"
    def __str__(self):           # for users / print()
        return f"({self.x}, {self.y})"
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

p, q = Point(1, 2), Point(3, 4)
print(p)            # uses __str__
print([p, q])       # lists use __repr__
print(p + q, p == Point(1, 2))
(1, 2) [Point(1, 2), Point(3, 4)] (4, 6) True

These double-underscore ("dunder") methods let your objects work with print, ==, +, len(), sorting and more. The Coding Python app's "operator overloading" example goes further.

Inheritance and super()

A subclass inherits everything from its parent and can add or override behaviour:

class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."
    def intro(self):
        return f"{self.name} says {self.speak()}"

class Dog(Animal):
    def speak(self):
        return "Woof"

class Puppy(Dog):
    def __init__(self, name, age_weeks):
        super().__init__(name)      # run parent constructor
        self.age_weeks = age_weeks
    def speak(self):
        return super().speak() + " (squeaky)"

for a in [Animal("Generic"), Dog("Rex"), Puppy("Bit", 8)]:
    print(a.intro())
print(isinstance(Puppy("x", 1), Animal), issubclass(Dog, Animal))
Generic says ... Rex says Woof Bit says Woof (squeaky) True True

Notice intro() is defined once in Animal yet calls the right speak() for each subclass. That is polymorphism.

Properties: computed and validated attributes

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def fahrenheit(self):
        return self.celsius * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5 / 9

t = Temperature(100)
print(t.fahrenheit)       # looks like an attribute, runs a method
t.fahrenheit = 32
print(t.celsius)
212.0 0.0
Model your own class in the app's editor — the Coding Python app runs real Python 3 on your phone, with examples, quizzes and challenges built in.
Get it free

Dataclasses: less boilerplate

For classes that mainly hold data, @dataclass writes __init__, __repr__ and __eq__ for you:

from dataclasses import dataclass, field

@dataclass(order=True)
class Book:
    title: str
    pages: int
    tags: list = field(default_factory=list)

a = Book("Fluent Python", 1000, ["advanced"])
b = Book("Automate", 500)
print(a)
print(a == Book("Fluent Python", 1000, ["advanced"]))
print(sorted([a, b])[0].title)
Book(title='Fluent Python', pages=1000, tags=['advanced']) True Automate

Classes are a big topic; you can go a long way with just __init__, a few methods and __repr__. Next: what to do when things go wrong — errors and exceptions.

Frequently asked questions

What is self in Python?

self is the instance a method is operating on. It is the first parameter of every instance method and Python fills it in automatically when you call obj.method().

What is the difference between a class and an object?

A class is the blueprint (Account); an object is a concrete thing made from it (Ada's account with balance 120). One class can create many objects.

What does __init__ do?

__init__ is the initializer that runs when an object is created. It usually sets the instance attributes from the arguments you pass to the class.

When should I use inheritance?

When one class genuinely is a specialised kind of another (Dog is an Animal). If you just want to reuse functionality, composition (holding another object as an attribute) is often simpler.