Python Classes and Objects

Model real things with classes: attributes, the __init__ method, instance methods, self, class attributes and __str__.

Lesson 34 of 107Advanced18 min read

As programs grow, you often have several pieces of data that belong together and functions that work on that data. A bank account has an owner and a balance, and you deposit into it and withdraw from it. You could store the data in a dictionary and write separate functions, but Python offers a tidier option: a class.

A class is a blueprint for making objects. It describes what data each object holds, called its attributes, and what it can do, called its methods. From one Account class you can create as many account objects as you like, each with its own owner and balance. This style is called object-oriented programming (OOP).

You've been using objects all along: strings, lists and dictionaries are objects with methods such as upper() and append(). In this lesson you'll write your own classes, understand the famous self parameter, and give your objects a readable printed form.

Defining a class and creating objects

Python
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

rex = Dog("Rex", 3)
bella = Dog("Bella", 5)
print(rex.name, bella.age)    # Rex 5
  • class Dog: starts the definition. Class names use CapWords by convention.
  • __init__ is a special method that runs automatically whenever a new object is created. It sets up the object's starting attributes.
  • Calling the class like a function, Dog("Rex", 3), creates a new object, also called an instance.
  • Each object has its own attributes, reached with a dot: rex.name.

What self means

Every method's first parameter is self: the particular object the method is working on. When you call rex.bark(), Python quietly turns it into Dog.bark(rex), so inside the method self is rex.

Python
class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        return f"{self.name} says woof!"

self.name = name stores the argument on the object so other methods can use it later. Forgetting self. is a classic mistake: a plain name = name inside __init__ creates a temporary local variable that disappears when the method ends.

Methods that change the object

Methods can read and update the object's attributes, which keeps all the rules about the data in one place:

Python
class Account:
    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

Because the check lives inside deposit(), no part of your program can accidentally add a negative amount, as long as it uses the method. Python doesn't strictly stop people changing attributes directly, but by convention a name starting with an underscore, such as _balance, means "internal: please use the methods".

Class attributes and __str__

An attribute defined directly in the class body, outside any method, is a class attribute, shared by every object. It suits values that are the same for all instances, such as an interest rate.

Printing an object normally shows something unhelpful like <__main__.Account object at 0x10f3>. Define __str__ to choose what print() and str() display:

Python
def __str__(self):
    return f"{self.owner}: £{self.balance:.2f}"

Methods with double underscores on both sides are called dunder methods. Python calls them automatically in particular situations, and they let your objects work naturally with built-in functions.

How attribute lookup works

When you read obj.attribute, Python first looks on the object itself, then on its class. That explains how class attributes are shared, and why assigning through an object behaves differently:

Python
class Account:
    interest_rate = 0.02        # class attribute

a = Account()
b = Account()
a.interest_rate = 0.05          # creates an attribute on a only
print(a.interest_rate, b.interest_rate, Account.interest_rate)
# 0.05 0.02 0.02

Assigning a.interest_rate didn't change the class; it gave a its own attribute that hides the shared one. To change the rate for everyone, assign to Account.interest_rate.

Objects are also compared by identity by default: two separate Account objects with identical balances are not == unless you define __eq__. The dataclasses lesson shows how to get sensible comparison for free, and magic methods covers __eq__, __repr__ and the rest.

Python Classes Syntax

Class syntax

class ClassName:
    shared_value = 0                # class attribute

    def __init__(self, param):
        self.attribute = param      # instance attribute

    def method(self, arg):
        return self.attribute + arg

    def __str__(self):
        return f"ClassName({self.attribute})"

obj = ClassName(value)
obj.method(arg)

Python Classes Summary

  • A class is a blueprint; an object (instance) is created by calling the class.
  • __init__(self, ...) sets up each new object's attributes.
  • self is the object a method is working on; store data with self.attribute.
  • Methods keep the rules for changing an object's data in one place.
  • Class attributes are shared by all instances; __str__ controls how an object prints.

Python Classes Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A simple bank account

Python
class Account:
    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:
            return False
        self.balance -= amount
        return True

    def __str__(self):
        return f"{self.owner}: £{self.balance:.2f}"

acc = Account("Sam", 50)
acc.deposit(25)
print(acc.withdraw(100))
print(acc.withdraw(30))
print(acc)
Output
False
True
Sam: £45.00
Line-by-line explanation
  1. Lines 2 to 4Each account starts with an owner and an optional balance.
  2. Lines 9 to 13The method refuses withdrawals larger than the balance and reports success with True or False.
  3. Lines 15 to 16__str__ decides what print(acc) shows.
  4. Lines 19 to 2150 + 25 = 75; the 100 withdrawal fails, the 30 succeeds, leaving 45.
Example 2

Several objects with a class attribute

Python
class Player:
    max_health = 100

    def __init__(self, name):
        self.name = name
        self.health = Player.max_health

    def take_hit(self, damage):
        self.health = max(0, self.health - damage)
        return self.health == 0

    def __str__(self):
        return f"{self.name} ({self.health}/{Player.max_health})"

team = [Player("Ana"), Player("Ben")]
team[0].take_hit(30)
knocked_out = team[1].take_hit(120)

for p in team:
    print(p)
print("Ben knocked out?", knocked_out)
Output
Ana (70/100)
Ben (0/100)
Ben knocked out? True
Line-by-line explanation
  1. Line 2A class attribute shared by every player.
  2. Line 9max(0, ...) stops health from going below zero.
  3. Line 15Objects can be stored in a list like any other value.
Example 3

A shopping basket made of objects

Python
class Item:
    def __init__(self, name, price, qty=1):
        self.name = name
        self.price = price
        self.qty = qty

    def total(self):
        return self.price * self.qty

class Basket:
    def __init__(self):
        self.items = []

    def add(self, item):
        self.items.append(item)

    def total(self):
        return sum(item.total() for item in self.items)

basket = Basket()
basket.add(Item("Notebook", 3.50, 2))
basket.add(Item("Pen", 1.20, 5))
print(f"{len(basket.items)} lines, total £{basket.total():.2f}")
Output
2 lines, total £13.00
Line-by-line explanation
  1. Lines 7 to 8Each item knows how to work out its own total.
  2. Line 12A basket object holds a list of item objects.
  3. Lines 17 to 18The basket's total asks each item for its total and adds them up.

Common Python Classes Mistakes

Forgetting self in a method definition

Don't
class Dog:
    def bark():
        return "Woof"

Dog().bark()   # TypeError: bark() takes 0 positional arguments but 1 was given
Do
class Dog:
    def bark(self):
        return "Woof"

Not storing values on self

Don't
class Dog:
    def __init__(self, name):
        name = name

print(Dog("Rex").name)   # AttributeError
Do
class Dog:
    def __init__(self, name):
        self.name = name

Using a mutable class attribute for per-object data

Don't
class Basket:
    items = []          # shared by every basket!

a, b = Basket(), Basket()
a.items.append("pen")
print(b.items)          # ['pen']
Do
class Basket:
    def __init__(self):
        self.items = []  # each basket gets its own list

Python Classes in Real Projects

Classes in real Python code

Web frameworks like Django describe database tables as classes, so a Product class becomes a products table and each object is one row. Games use classes for players, enemies and levels, and libraries such as datetime and pathlib give you objects with useful methods. Knowing how classes work makes all of these libraries much easier to understand.

Python Classes FAQ

What is the difference between a class and an object in Python?

A class is the definition: it describes what attributes and methods its objects will have. An object, also called an instance, is one concrete thing made from that class, with its own attribute values. Dog is the class; Dog("Rex", 3) creates an object.

What does self mean in Python?

self is the object the method was called on. When you write rex.bark(), Python passes rex as the first argument, so inside bark you reach its data through self.name. The name self is a strong convention rather than a keyword, but always use it.

Is __init__ a constructor?

In practice, yes: it is where you set up a new object's attributes. Strictly speaking, Python creates the empty object first with __new__ and then calls __init__ to initialise it. You will almost never need to write __new__ yourself.

What is the difference between __str__ and __repr__?

__str__ gives a friendly description for users and is used by print(). __repr__ gives an unambiguous description for developers, shown in the interactive shell and inside lists, ideally looking like code that recreates the object. If you only define one, define __repr__; Python falls back to it when __str__ is missing.

Does Python have private attributes?

Not enforced ones. A single leading underscore, _balance, marks an attribute as internal by convention. A double leading underscore, __balance, triggers name mangling, which makes accidental access from outside or from subclasses harder but still not impossible. Python relies on conventions rather than access modifiers like Java's private.

Python Classes Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the class so each object stores its title.

class Book:
    def (self, title):
        .title = title
Predict the outputeasy

What does this print?

class Cat:
    def __init__(self, name):
        self.name = name

a = Cat("Tom")
b = Cat("Kit")
print(b.name)
Predict the outputmedium

What does this print?

class Counter:
    def __init__(self):
        self.count = 0
    def click(self):
        self.count += 1
        return self

c = Counter()
c.click().click().click()
print(c.count)
Multiple choicemedium

What is self in a method?

Multiple choicehard

Which method controls what print(obj) shows?

Python Classes Quiz

5 questions to check what you learned.

  1. How do you create an object from a class called Car?

    • new Car()
    • create Car
    • Car.new()
    • Car()
  2. When does __init__ run?

    • Each time a new object is created
    • When the program starts
    • When the object is printed
    • Only once per class
  3. What is a class attribute?

    • An attribute created inside __init__
    • A private attribute
    • An attribute defined in the class body, shared by all instances
    • A method
  4. What does a leading underscore in _balance mean?

    • It's a syntax error
    • By convention, it's internal and should be changed through methods
    • Python makes it read-only
    • It's a class attribute
  5. Why is items = [] in a class body risky for per-object data?

    • Lists can't be attributes
    • It slows the class down
    • The same list is shared by every object of the class
    • It deletes the list each time