Home › Session 04
Objects · Session 04

Classes & Objects

Think of a class as a recipe and an instance as one cake. self just means "this cake". Once you see where the ingredients live and where the instructions live, object-oriented Python stops feeling like magic.

~45 minutes
5 interactive demos
Builds on Sessions 01 to 03
Learning goal

Data on the cake. Instructions on the recipe.

Each instance keeps its own attributes. Methods are shared on the class and aren't copied onto every object. self is just that instance being passed in. Reading a name walks from the instance to the class to the parent classes.

class instance bound method __dict__ __init__ MRO
Recommended path
1 Watch construction. Class first, then instance, then __init__ writing attributes.
2 Ask where the name lives. Instance dict, class dict, or a bound method object?
3 Reuse earlier rules. Storing a list on self is still aliasing. Inheritance is just a longer lookup.
Interactive lab

Follow a class from definition to a method call

Watch the recipe appear, then one cake, then the cake getting its own labels. The memory panel shows the class, the instance and the call frames. Each instance has a __class__ link back to its type, and every arrow you see in a __dict__ row is a reference to another box on the same panel.

Tip: when you see self, find the instance it points at. The same address means the same object.
1 Choose a demo
›
2 Step with → or Play
›
3 Watch class vs instance dicts
›
4 Read the model below
Call stack: names, including self
Heap: class, instance, method. __class__ is the type link
→ next ← back Space play R reset
python
1 / 1
Memory
stack heap
1

Initial state: only the global frame

Press Play or use Next to watch a class become an instance.

What is self?

In plain words self isn't a magic keyword. It means "the object I'm talking about right now". When you write p = Point(3, 4), Python hands that new point into __init__ as self so the function can label it.

It's the conventional name for the instance this method is running on. Python passes it for you on p = Point(3, 4) or g.hello("Ada"). You could spell the first parameter anything you like. The runtime only cares that it's bound to the instance.

Class object

The indented body runs first and makes the function objects. Then Python builds the class from that namespace and binds the name Point to it.

Instance + __init__

Calling the class allocates an instance, then calls __init__(self, ...) with that instance as self.

Instance __dict__

self.x = 3 writes into the instance's attribute mapping. The class is unchanged.

Model
You could name it anything
The first parameter of an instance method is just a parameter. Python programmers write self by convention, but the runtime doesn't require that spelling. What matters is that it's bound to the instance.
Attribute lookup

Instance first, then class

In plain words Reading a name is like asking "does this cake have that label?" If not, ask the recipe. If the recipe inherited from another recipe, keep asking. Writing a name usually just sticks a new label on this cake. It doesn't edit the shared recipe.

Reading a.lives searches a.__dict__, then the class, then the method resolution order (MRO). Writing a.score = 10 doesn't search at all. It writes into the instance dict.

Reading

Walk outward

Instance dict → class dict → bases. That is why every Player() can read lives even when the instance dict is empty.

Writing

Usually stay local

a.score = 10 creates an instance attribute. It does not change Player.score or b.score.

Care
a.lives = 1 is not the same as Player.lives = 1
The first shadows lives only on a. The second changes the shared class attribute. If lives were a mutable list stored on the class, a.lives.append(1) would mutate the shared list. Lookup finds the class object, then the mutation changes it.
Edge
Two places the simple rule bends
"Instance first" is the right default and it covers almost everything you'll write. Two exceptions are worth knowing about. If the class defines the name as a @property, the class wins and the instance dict is never consulted. That's why a.lives = 1 would raise AttributeError: property 'lives' of 'Player' object has no setter instead of shadowing it. Properties are data descriptors, and those outrank the instance. Plain methods are non-data descriptors and don't, so a value forced into a.__dict__['speak'] would still win over the class function. The other exception is a class that declares __slots__, which can have no instance __dict__ at all. Its attributes sit in fixed slots. The search order is unchanged, the box we keep drawing just isn't there.
Methods

Accessing a function on an instance binds self

In plain words The greeting function lives on the recipe, shared by every greeter. When you say g.hello, Python staples g onto that function so you don't have to pass it yourself. That staple is why the definition has two parameters and the call looks like it passes one.

g.hello is a bound method: a small object that remembers the function and the instance. Calling it supplies self automatically.

Expression What you get Who fills self?
Greeter.hello The raw function on the class You must pass the instance: Greeter.hello(g, "Ada")
g.hello A bound method The method object already holds g
g.hello("Ada") A call through that bound method Python, automatically
Try this in a REPL

After g = Greeter(), print g.hello and Greeter.hello. Then compare g.hello.__self__ is g. You should see True.

type and __class__

Every instance knows its class

In plain words A cake doesn't carry a photocopy of the whole recipe. It carries a note that says "I was made from Point". When you ask it to do something it doesn't have on itself, it follows that note back to the recipe.

That note is type(p) / p.__class__. Methods stay on the class. Lookup starts on the instance, then follows this pointer.

p
->
Point() @ 0x7f720010 __class__ -> Point __dict__ { x, y }
Point
->
class Point @ 0x7f7100a0 __init__ -> function total -> function

In plain words Making a point is two jobs: first get a blank cake tin, then write the ingredients on it. You almost never handle the tin yourself. Point(3, 4) does both, then hands you the finished cake.

Those two jobs are __new__ (allocate) and __init__ (initialise). The instance already exists by the time __init__ gets it as self, and Point(3, 4) hands you that instance, not whatever __init__ returned.

Allocate

When you would override __new__

Almost never. The cases that exist are real, though: subclassing an immutable type such as int, str or tuple, where the value has to be decided at allocation time, and singleton-style classes that return an existing object instead of a fresh one.

Initialise

__init__ must return None

This is enforced, not just a convention. Slip a return self or return 5 into __init__ and Python raises TypeError: __init__() should return None, not 'int'. Its job is to fill the object in, not to produce one.

Inheritance

A subclass does not copy methods

In plain words A puppy doesn't get a photocopied list of every parent trick. If it doesn't know speak, it asks its parent recipe. The trick still runs on this puppy, not on some generic animal.

class Dog(Animal) creates a new class and records Animal in __bases__. The MRO is the search list: Dog → Animal → object. d.speak walks that list, then binds self to d.

1 · d instance __dict__ miss
2 · Dog class namespace, where pass copied nothing miss
3 · Animal finds speak, then binds self to d hit
Model
self is still the subclass instance
The function was found on Animal, but the bound method remembers d. That's how a base method can read subclass attributes: it receives the actual instance. If Dog later defines its own speak, the lookup stops there.
Try this in a REPL

After the inheritance demo, print Dog.__mro__ and d.speak.__func__ is Animal.speak. You should see the search order and True.

Pitfalls

The bugs this model prevents

In plain words If something odd happens with a class, ask three everyday questions. Did I write on this one object or on the shared recipe? Did I share a list by accident? Did I assume each object got its own copy of the methods?

Those map to lookup versus assignment, mutable class attributes, and the fact that methods live on the class, plus aliasing through self.

A list on the class is one list for every instance

class Team: members = [] then a.members.append("Ada") mutates the class attribute. Lookup found the shared list; mutation changed it. Put self.members = [] inside __init__ instead.

self.items = items does not copy

Demo 4 is Session 03 in a new costume. If the caller still holds the list, an append on the instance is visible outside. Copy when you need a private container.

Methods are not copied onto each instance

Creating a thousand Point objects doesn't create a thousand __init__ functions. Instances share the class. That's why __class__ matters.

What you should be able to draw

A class (the recipe) with shared methods. Instances (the cakes) with their own __dict__ and a __class__ arrow back. A bound method that staples the instance onto the function. A subclass with __bases__ that asks the parent instead of copying methods. And self in a call frame pointing at the same instance the caller used.

Check your understanding

Can you predict the memory?

Ask yourself: is this on the cake, on the recipe, or stapled onto a method?

1. After p = Point(3, 4), where does the name x live?
self.x = x copies a reference into the instance dict. The __init__ locals disappear when the call returns.
2. a and b are two Player() instances. You run Player.lives = 2. What do they see?
Lookup misses on the instance and finds lives on the class. An instance assignment would shadow that name for that instance only.
3. Why can you call g.hello("Ada") when hello is defined as hello(self, name)?
Attribute access on the instance creates a bound method. The call only needs the remaining arguments.
4. class Team: members = [] then a = Team(); b = Team(); a.members.append("Ada"). What is b.members?
Reading a.members misses on the instance and finds the class list. append mutates that one object. Put self.members = [] in __init__.
5. d is a Dog() and speak is defined only on Animal. Where does d.speak come from?
Inheritance is a search path (__class__, then __bases__ / the MRO). The bound method still remembers the subclass instance.