What is self?
In plain words
self is not a magic keyword. It means “the object I am 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 is 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; the runtime only cares that it is 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.
self by convention. The runtime does not require that spelling. What matters
is that it is bound to the instance.
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 does not edit the shared recipe.
Reading a.lives searches a.__dict__, then the class, then the
method resolution order (MRO).
Writing a.score = 10 does not search — it writes into the instance dict.
Walk outward
Instance dict → class dict → bases. That is why every Player() can read lives even when the instance dict is empty.
Usually stay local
a.score = 10 creates an instance attribute. It does not change Player.score or b.score.
a.lives = 1 is not the same as Player.lives = 1lives 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 mutation changes it.
@property, the class wins and the
instance dict is never consulted — that is why a.lives = 1 would raise
AttributeError: property 'lives' of 'Player' object has no setter instead of quietly
shadowing it. Properties are data descriptors, and those outrank the instance;
plain methods are non-data descriptors and do not, so a value forced into
a.__dict__['speak'] would still win over the class function. The other exception:
a class that declares __slots__ can have no instance __dict__ at all —
its attributes sit in fixed slots. The search order is unchanged; the box we keep drawing is
simply not there.
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 do not 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 |
After g = Greeter(), print g.hello and Greeter.hello.
Then compare g.hello.__self__ is g. You should see True.
Every instance knows its class
In plain words A cake does not 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 does not 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.
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__ (initialize).
The instance already exists by the time __init__ gets it as self,
and Point(3, 4) hands you that instance — not whatever __init__ returned.
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.
__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.
A subclass does not copy methods
In plain words
A puppy does not get a photocopied list of every parent trick.
If it does not 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.
__dict__
miss
pass copied nothing
miss
speak, then binds self to d
hit
self is still the subclass instanceAnimal, but the bound method remembers d.
That is how a base method can read subclass attributes: it receives the actual instance.
If Dog later defines its own speak, lookup stops there.
After the inheritance demo, print Dog.__mro__ and d.speak.__func__ is Animal.speak.
You should see the search order and True.
The bugs this model prevents
In plain words If something “weird” 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 think each object got its own copy of the methods?
Those map to lookup vs 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,
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 does not create a thousand __init__
functions. Instances share the class. That is why __class__ matters.
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.