Python The self Keyword
In Python, self is the name conventionally given to the first parameter of every instance method — it is how an object refers to itself from inside its own methods. When you call a method on an object, Python automatically passes that object in as the first argument, and by convention we name that parameter self. Without it, a method would have no way to read or change the attributes that belong to the specific object it was called on, or to call that object’s other methods. Understanding self deeply is really understanding how Python’s entire object model works.
Overview: What self Actually Is
A class is a blueprint. When you instantiate it, Python creates an object in memory and that object needs a way to store its own data (its attributes) separately from every other object created from the same class. Methods defined inside a class are just ordinary functions — but when you access one through an instance, Python does something special: it turns the function into a bound method, automatically supplying the instance as the first argument. That first argument is what we name self.
Here is the important part: self is not a reserved keyword in Python, unlike this in Java, C++, or JavaScript. You could technically name that first parameter anything — this, me, obj — and the code would still run. But every piece of Python code you will ever read, and every style guide (including PEP 8), uses self. Deviating from this convention makes your code confusing to every other Python developer, so treat it as a hard rule even though the interpreter does not enforce it.
When Python looks up instance.method(args), it actually performs two steps behind the scenes: it finds method on the class (not the instance), and because functions are descriptors in Python, accessing a function through an instance returns a bound method object that already has the instance baked in as the first argument. That bound method is then called with the remaining arguments. In other words, instance.method(args) is exactly equivalent to Class.method(instance, args). This is why every instance method’s signature must include self as its first parameter — Python is going to supply it automatically, and the method definition must have a place to receive it.
Inside a method, self.attribute_name reads or writes a value stored in the instance’s own namespace (technically its __dict__). If the attribute isn’t found there, Python falls back to looking it up on the class (which is how class attributes — values shared by every instance — work) and then up the method resolution order for inherited classes. This is also why two different objects created from the same class can hold completely different data: each has its own self, and therefore its own private storage.
Syntax
The general shape of an instance method always starts with self as the first formal parameter:
class ClassName:
def method_name(self, other_param1, other_param2=default):
# use self.attribute to read/write instance data
# use self.other_method() to call another instance method
...
self— the first parameter of every instance method; refers to the specific object the method was called on. You never pass it explicitly when callinginstance.method()— Python supplies it.self.attribute_name— reads or assigns an attribute that belongs to this particular instance.self.other_method()— calls another method defined on the same class, on the same instance.- other parameters — any additional arguments the method needs, supplied normally by the caller after
self.
Two related conventions worth knowing now: inside a @classmethod, the first parameter is named cls (it receives the class itself, not an instance), and a @staticmethod receives neither self nor cls because it doesn’t operate on instance or class state at all.
Examples
Example 1: Storing and using per-instance data
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print(f"{self.name} says Woof!")
buddy = Dog("Buddy", "Golden Retriever")
rex = Dog("Rex", "German Shepherd")
buddy.bark()
rex.bark()
print(buddy.name, buddy.breed)
Output:
Buddy says Woof!
Rex says Woof!
Buddy Golden Retriever
Both buddy and rex are built from the same Dog class, but each has its own self.name and self.breed. When buddy.bark() runs, self inside that call is buddy; when rex.bark() runs, self is rex. That is the entire mechanism that makes object-oriented state possible in Python.
Example 2: self reveals how method calls really work
class Dog:
def __init__(self, name):
self.name = name
def rename(self, name):
print(f"Renaming {self.name} to {name}")
self.name = name
d = Dog("Buddy")
d.rename("Max")
print(d.name)
# Calling through the class explicitly, passing the instance manually
Dog.rename(d, "Charlie")
print(d.name)
Output:
Renaming Buddy to Max
Max
Renaming Max to Charlie
Charlie
This example proves the equivalence directly: d.rename("Max") and Dog.rename(d, "Charlie") do exactly the same kind of thing. In the first call, Python auto-binds d as self. In the second call, we bypass that convenience and pass the instance manually as the first positional argument to the unbound function stored on the class. Both work identically — this is proof that self is just a regular parameter receiving a regular argument, nothing magical.
Example 3: A realistic class with several self-using methods
class BankAccount:
interest_rate = 0.02 # class attribute shared by all accounts
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self
def withdraw(self, amount):
if amount > self.balance:
print(f"Insufficient funds for {self.owner}")
else:
self.balance -= amount
return self
def apply_interest(self):
self.balance += self.balance * self.interest_rate
return self
def __repr__(self):
return f"BankAccount(owner={self.owner!r}, balance={self.balance:.2f})"
acc = BankAccount("Alice", 100)
acc.deposit(50).withdraw(30).apply_interest()
print(acc)
Output:
BankAccount(owner='Alice', balance=122.40)
Here self is used in three distinct ways: self.balance and self.owner store per-account data; self.interest_rate reads a class attribute (shared by every account, found via fallback lookup since it isn’t in the instance’s own __dict__); and each method return self, which lets calls be chained together (deposit(...).withdraw(...).apply_interest()) because each call returns the very same object it was invoked on.
How self Works Under the Hood
When you write class Dog: and define def bark(self): ... inside it, Python stores bark as a plain function object as an attribute of the class Dog — nothing instance-specific has happened yet. The binding to a particular object only happens at attribute access time:
- You write
buddy.bark. Python looks forbarkfirst on the instancebuddy(not found), then on its classDog(found — it’s a function). - Because functions implement the descriptor protocol (
__get__), accessing a function through an instance doesn’t return the raw function — it returns a new, temporary bound method object that remembers both the function and the instance (buddy). - You call that bound method with
(). The bound method insertsbuddyas the first argument automatically and calls the original function, which receives it asself.
This is also why calling a method on the class itself, like Dog.bark(buddy), works without any binding magic: Dog.bark retrieves the plain, unbound function, so you must supply the instance yourself as the first argument — exactly as Example 2 demonstrated.
Common Mistakes
Mistake 1: Forgetting self in the method definition
If you omit self from a method’s parameter list but still call it the normal way, Python still auto-supplies the instance as the first argument — it just has nowhere to put it, so the extra argument collides with your other parameters (or, with zero declared parameters, you get a TypeError about too many arguments):
class Dog:
def bark(): # missing self
print("Woof!")
d = Dog()
d.bark() # TypeError: bark() takes 0 positional arguments but 1 was given
The fix is simple: every instance method needs self as its first parameter, full stop, even if the method body never uses it.
Mistake 2: Forgetting the self. prefix when assigning an attribute
A very common bug is assigning to a local variable that merely looks like an attribute, instead of assigning to self.attribute. The code runs without any error, which makes it especially dangerous — the class silently fails to update its own state:
class Counter:
def __init__(self, start=0):
self.count = start
def increment(self):
count = self.count + 1 # forgot self on the left side
print(count)
c = Counter(5)
c.increment()
c.increment()
print(c.count)
Output:
6
6
5
Notice that increment() prints 6 both times, and c.count is still 5 at the end — the local variable count was recalculated and printed each time, but it never actually updated the object’s real attribute, because the code never wrote to self.count. The correct version is self.count = self.count + 1 (or self.count += 1).
Best Practices
- Always name the first parameter of an instance method
self— it is a universal convention, and any other name will confuse readers even though Python allows it. - Use
self.attributeconsistently for anything that should be per-instance state; never rely on a bare local variable with the same name as an attribute. - Reserve class-level attributes (defined directly in the class body, without
self) for values genuinely shared across all instances, such as constants or defaults — and remember thatself.attribute = valueinside a method creates or shadows an instance attribute rather than modifying the shared class attribute. - Use
@classmethodwithclsinstead ofselfwhen a method needs to work with the class itself (e.g. alternative constructors), and@staticmethodwith neither when a method doesn’t need instance or class data at all. - Return
selffrom a method only when you deliberately want to support method chaining (a fluent interface) — don’t do it as a habit for every method, since it can make code less predictable. - Never call instance methods via the unbound
Class.method(instance, ...)form in production code; it’s useful for teaching how binding works, butinstance.method(...)is the idiomatic, readable form.
Practice Exercises
- Write a
Rectangleclass whose__init__takeswidthandheightand stores them viaself. Add anarea(self)method and aperimeter(self)method that useself.widthandself.heightto compute and return their results. - Create a
Playlistclass with an instance attributeself.songs(a list, initialized empty in__init__). Add anadd_song(self, title)method that appends toself.songsand returnsself, so that two calls can be chained:playlist.add_song("A").add_song("B"). - Take the buggy
Counterclass from the Common Mistakes section and fix theincrementmethod so that calling it twice on aCounter(5)instance correctly prints6then7, andc.countequals7afterward.
Summary
selfis the conventional name for the first parameter of every instance method — it refers to the specific object the method was called on.selfis not a reserved keyword; Python enforces the position, not the name, but every Python developer usesselfby convention.instance.method(args)is automatically translated by Python intoClass.method(instance, args)via the descriptor protocol, which is whyselfmust be declared explicitly in every method signature.self.attributereads or writes data that belongs to one specific instance, stored in that instance’s own namespace.- Forgetting
selfin a method definition causes aTypeError; forgetting theself.prefix when assigning silently creates a disconnected local variable instead of updating the object. clsreplacesselfin@classmethod, and neither appears in@staticmethod.
