Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate ~28 min· topic 1 of 12

Topic 8.1

Classes and Objects

In one line

A class is a blueprint that describes what a kind of thing knows (its attributes) and what it can do (its methods); an object, also called an instance, is one real thing built from that blueprint. __init__ sets up each new object, and self is the name a method uses for the object it was called on.

Think of it like this

A cookie cutter and cookies. The cutter is the shape; it is not a cookie. Press it into dough ten times and you get ten separate cookies with the same shape. You can decorate one cookie with sprinkles and the others stay plain. A class is the cutter. Each object (also called an instance) is one cookie made from it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Class
A blueprint that describes a kind of object: its data and its behaviour.
Object (instance)
One real thing built from a class. Dog("Rex", 3) makes one Dog instance.
Attribute
A named piece of data attached to an object, such as rex.name.
Method
A function defined inside a class, called on an object, such as rex.speak().
`__init__`
The initialiser: Python calls it right after creating a new object, so it can set the starting attributes.
`self`
The first parameter of a method. It is the object the method was called on.
Dunder
A name with double underscores on both sides, like __init__. Python calls these for you.
Instantiate
To create an object from a class by calling the class: Dog("Rex", 3).
OOP
Object-oriented programming: organising a program as objects that hold data and behaviour together.

Step by step

01Why a class at all

Suppose you track dogs with plain variables or dictionaries. A dictionary {"name": "Rex", "age": 3} holds the data, but nothing stops you writing "nmae" by mistake, and the code that works on dogs is scattered in separate functions.

A class gathers the data and the functions that use it into one named thing. That gives you one place to read, one place to fix and a real type that tools and teammates can recognise.

02Write the class and make objects

class Dog: starts the blueprint. Inside, def __init__(self, name, age) sets the starting attributes. Notice that you call Dog("Rex", 3) with two arguments: Python supplies self for you.

main.pywhole filepython
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        return self.name + " says Woof!"


rex = Dog("Rex", 3)
bella = Dog("Bella", 5)
print(rex.speak())

03What Dog(...) does, step by step

Calling a class runs three steps. First Python creates a new, empty object. Second it calls Dog.__init__(that_object, "Rex", 3), which fills in the attributes. Third it hands the finished object back, and rex is a name for it.

Each object has its own small dictionary of attributes, while the methods live once on the class.

What Dog(...) does, step by stepdiagram
Rendering diagram…

04self is the object before the dot

rex.speak() is shorthand for Dog.speak(rex). Python looks up speak on the class and passes the object as the first argument. That is why def speak(self) has one parameter even though you call it with none.

If you leave self out of the definition, the call fails with a TypeError: Python still passes the object, and the method has nowhere to put it.

main.pywhole filepython
rex.speak()          # what you write
Dog.speak(rex)       # what Python runs: same result

05Methods change the object they belong to

A method can change self's attributes. rex.birthday() adds one to Rex's age only. Bella's age is untouched, because each object owns its attributes.

A method can also return self, which lets you chain calls: counter.add().add().add(). Many libraries use this style.

main.pywhole filepython
    def birthday(self):
        self.age = self.age + 1

06Everything is an object

type() tells you which class an object was built from. 42 is an int, a function is a function, and a class is a type. Even (5).bit_length() works because the number 5 is an object with methods.

This is why Python feels uniform: you can pass a class to a function, store a function in a list or print an attribute of a number. The next topics build on this.

07Printing an object needs __repr__

If you print an object of your own class without any extra code, Python shows something like <__main__.Dog object at 0x7f3a...>. The part after at is a memory address, which tells you nothing and changes on every run.

Define __repr__ to return a helpful string, ideally one that looks like the code that makes the object. Topic 8.7 covers it fully; this topic uses it early so you can see your objects.

main.pywhole filepython
    def __repr__(self):
        return "Dog(name=" + repr(self.name) + ", age=" + str(self.age) + ")"

08Habits that save you later

Name classes in CapWords (BankAccount) and everything else in snake_case. Set every attribute inside __init__, even if the starting value is None, so a reader sees the full shape of the object in one place.

Not everything needs a class. If you only need to group three values, a dictionary or (Topic 8.8) a dataclass is shorter. Use a class when data and behaviour belong together.

Try it yourself

  1. 1

    Make a third dog

    In the first example, add max_dog = Dog("Max", 1) and print max_dog.speak(). Before running, predict what it prints. Then call max_dog.birthday() twice and print its age.

  2. 2

    Add your own method

    Add a method is_puppy(self) that returns True when self.age < 2. Call it on each dog. A method is only a function that takes self first.

  3. 3

    Call a method the long way

    In the second example the call m(c2) uses Counter.add directly. Try Counter.add(c) on the first counter and print c.count. You are doing by hand what c.add() does for you.

Code & diagrams

A Dog class with two objects New tab

Press Run. Then add a third dog and call its methods. Notice that Rex's birthday does not change Bella.

Sign in to run this example in your browser.

Expected output

Rex says Woof!
Bella says Woof!
Rex is now 4
Bella is still 5
Dog(name='Rex', age=4)
<class '__main__.Dog'>
False
Everything is an object, and self is just a parameter New tab

The loop prints the first 20 characters of each repr, so the output stays the same on every run. Try adding more values to the list.

Sign in to run this example in your browser.

Expected output

count: 3
42                     -> int
'hi'                   -> str
[1, 2]                 -> list
<built-in function l   -> builtin_function_or_method
<class '__main__.Cou   -> type
<__main__.Counter ob   -> Counter
classes are objects too: <class 'type'>
int is a class: True
a function has attributes: len
(5).bit_length() = 3
count via Counter.add(c2): 2
extra ['count', 'label']
What an unprinted object looks like (not runnable)python

The address after at differs on every run, which is why runnable examples on this site define __repr__ before printing an object. The address shown here is only an example.

class Plain:
    pass

p = Plain()
print(p)

Output

<__main__.Plain object at 0x7f0c3a1b2d50>
Class and objectsdiagram
Rendering diagram…
A realistic class: a shopping cart New tab

Dogs are a toy. This is closer to real programs: each customer has their own cart (data) and the cart knows how to add items and total the bill (behaviour). Each cart keeps its own items list. Add a third customer and a method that counts items.

Sign in to run this example in your browser.

Expected output

ShoppingCart('Asha', 2 items) total: 75
ShoppingCart('Ben', 1 items) total: 500
{'name': 'pen', 'price': 10, 'quantity': 3}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget self in a method

Define def speak(): with no parameter and call rex.speak().

main.pywhole filepython
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak():
        return "Woof"


rex = Dog("Rex", 3)
rex.speak()
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 11, in <module>
rex.speak()
~~~~~~~~~^^
TypeError: Dog.speak() takes 0 positional arguments but 1 was given

Break #2

Create an object with a missing argument

Call Dog("Rex") when __init__ needs both a name and an age.

main.pywhole filepython
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age


rex = Dog("Rex")
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 7, in <module>
rex = Dog("Rex")
TypeError: Dog.__init__() missing 1 required positional argument: 'age'

Break #3

Read an attribute that was never set

Print rex.age when __init__ only sets name.

main.pywhole filepython
class Dog:
    def __init__(self, name):
        self.name = name


rex = Dog("Rex")
print(rex.age)
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 7, in <module>
print(rex.age)
^^^^^^^
AttributeError: 'Dog' object has no attribute 'age'

Myth vs fact

Myth

A class and an object are the same thing.

Fact

A class is the blueprint, and an object is one thing built from it. You write one Dog class and can create thousands of Dog objects, each with its own data.

Myth

self is a special Python keyword.

Fact

self is only the conventional name of the first parameter. Python would accept any name. Everyone uses self, so code with another name is hard to read.

Myth

Methods are copied into every object.

Fact

Methods are stored once, on the class. Each object keeps only its own attributes, and Python looks up the method on the class when you call it.

Myth

You must use classes to write real Python.

Fact

Plenty of good Python is plain functions and data. Classes are one tool, best when data and behaviour belong together. Topic 8.11 covers when not to use one.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Under the hood Dog("Rex", 3) calls type.__call__(Dog, "Rex", 3), which calls Dog.__new__ to allocate the object and then Dog.__init__ to fill it. You rarely write __new__; it matters for immutable types and singletons.

  • ▸

    By default each instance stores its attributes in a dictionary called __dict__. That is why you can add attributes freely, and also why plain objects use more memory than a tuple. Topic 8.12 shows how __slots__ removes it.

  • ▸

    A bound method such as rex.speak is itself an object that remembers rex. rex.speak.__self__ is rex and rex.speak.__func__ is the plain function on the class. This is the mechanism behind self.

  • ▸

    In AI code, small classes often wrap an API client or hold conversation state: one object per conversation, with a list of messages and methods like add_user_message. For pure data, a dataclass (Topic 8.8) is usually nicer than writing __init__ by hand.

Remember this

  1. 1

    You have used objects since Phase 1: "hi" is a str object, [1, 2] is a list object, and len is a function object. A class is how you add your own kinds. You write class Dog: followed by an indented block, and Python creates a new type called Dog. Calling it like a function, Dog("Rex", 3), builds a new Dog object.

  2. 2

    An object holds data in attributes: names attached to the object, written rex.name. It also has behaviour in methods: functions defined inside the class, called as rex.speak(). Data and behaviour that belong together live together, which is the whole idea of object-oriented programming (OOP).

  3. 3

    __init__ is the initialiser. Python calls it automatically right after it creates an empty object, passing the arguments you gave to Dog(...). Inside, you store the starting data: self.name = name. Its name has two underscores on each side, a pattern called a dunder (double underscore) that marks a method Python itself knows how to call. __init__ returns nothing; it only fills in the object.

  4. 4

    self is the first parameter of every normal method, and it is the object the method was called on. When you write rex.speak(), Python runs Dog.speak(rex), so self is rex. The name self is only a convention, but every Python programmer uses it, and so should you. This is why a method can say self.age + 1 and get that dog's age, not another dog's.

  5. 5

    Two objects of the same class are independent. Changing rex.age does not touch bella.age, because each object has its own set of attributes. They share only the class: the methods are stored once on the class and every instance uses them. rex is bella is False even if both have the same data, because they are different objects.

  6. 6

    In Python, everything is an object, including numbers, functions and the class itself. type(rex) is Dog, and type(Dog) is type: classes are objects too, built by type. Python is also flexible: you can add a new attribute to an existing object at any time (rex.owner = "Sam"). That flexibility is useful but makes typos silent, so set all attributes in __init__.

Explain it without notes

01

What is the difference between a class and an object?

02

What does __init__ do, and when is it called?

03

What is self, and why do methods take it explicitly?

04

What does it mean that "everything is an object" in Python?

Practice

01

Write a class Rectangle with attributes width and height, and methods area() and perimeter(). Print both for a 3 by 4 rectangle.

02

Write a class Counter with a count starting at 0, an add() method that increases it and returns self, and a reset() method. Add three times in a chain and print the count.

03

Write a class Book with title and pages, and a __repr__ that returns text like Book('Python', 300). Print a list of two books.

Trade-offs

  • ↔

    A class bundles data and behaviour, at the cost of more code. For two or three values with no behaviour, a dictionary or dataclass is shorter. Reach for a class when methods need to keep the data consistent.

  • ↔

    Free attribute creation is flexible but error-prone. You can add rex.owner any time, and you can also mistype rex.agee. Setting every attribute in __init__ and using dataclasses or __slots__ (Topics 8.8 and 8.12) trades some flexibility for safety.

Done when you can

  • Done when you can explain the difference between a class and an object with an example.

  • Done when you can write a class with __init__, attributes and two methods.

  • Done when you can explain why rex.speak() is the same as Dog.speak(rex).

  • Done when you can explain why two objects from the same class do not share attributes.

  • Done when you can say why printing a plain object shows a memory address, and how __repr__ fixes it.