self, Methods and Instance State
self is the object the method was called on. Python passes it automatically, and every confusing method error traces back to that one mechanism.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
What self actually is
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
c = Counter()
c.increment()
# What Python actually does:
Counter.increment(c)
print(c.count) # 2 - both calls workedc.increment() is shorthand for Counter.increment(c). The object before the dot becomes the first parameter. That is the entire mechanism, and it explains everything below.
c = Counter()
print(Counter.increment) # <function Counter.increment> - a plain function
print(c.increment) # <bound method Counter.increment of ...>
print(c.increment.__self__ is c) # True - the object it is bound toThe name self is a convention
class Counter:
def increment(this): # legal, and nobody writes it this way
this.count += 1Python only cares that the first parameter exists. Every Python programmer expects it to be called self, so use that name.
The two errors everyone meets
class Broken:
def greet(): # no self
return "hello"
b = Broken()
# b.greet()
# TypeError: greet() takes 0 positional arguments but 1 was givenThe object was passed as the first argument, and the method declared no parameter to receive it. Add self.
class Broken:
def __init__(self):
count = 0 # a LOCAL variable, discarded immediately
def increment(self):
self.count += 1 # AttributeError: no attribute 'count'Without self., the assignment creates a local name inside __init__ that disappears when the method returns. Every attribute needs the prefix.
Methods calling methods
class Order:
TAX_RATE = 0.18
def __init__(self, items):
self.items = items
def subtotal(self):
return sum(price * qty for price, qty in self.items)
def tax(self):
return self.subtotal() * self.TAX_RATE # self, even for a class attribute
def total(self):
return self.subtotal() + self.tax()
def summary(self):
return (
f"subtotal {self.subtotal():,.2f} "
f"tax {self.tax():,.2f} "
f"total {self.total():,.2f}"
)
order = Order([(100, 2), (50, 3)])
print(order.summary())A method reaches another method through self, never by bare name. Writing subtotal() inside tax would be a NameError.
Methods that return self
class QueryBuilder:
def __init__(self, table):
self.table = table
self.conditions = []
self.ordering = None
def where(self, condition):
self.conditions.append(condition)
return self # allows chaining
def order_by(self, column):
self.ordering = column
return self
def build(self):
query = f"SELECT * FROM {self.table}"
if self.conditions:
query += " WHERE " + " AND ".join(self.conditions)
if self.ordering:
query += f" ORDER BY {self.ordering}"
return query
print(
QueryBuilder("notes")
.where("status = 'published'")
.where("views > 100")
.order_by("created_at")
.build()
)Instance state and encapsulation by convention
class Account:
def __init__(self, owner, balance=0):
self.owner = owner # public
self._balance = balance # internal by convention
self.__pin = "1234" # name mangled
def deposit(self, amount):
if amount <= 0:
raise ValueError("deposit must be positive")
self._balance += amount
return self._balance
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("insufficient funds")
self._balance -= amount
return self._balance
def get_balance(self):
return self._balance
account = Account("Meera", 1000)
print(account._balance) # accessible - Python does not enforce privacy
# print(account.__pin) # AttributeError
print(account._Account__pin) # 1234 - the mangled name| Name | Means | Enforced? |
|---|---|---|
name | Public. Use it freely. | - |
_name | Internal. Do not rely on it. | No, convention only |
__name | Name mangled to _Class__name | Partly - it avoids collisions, not access |
Python has no private keyword. A single underscore is a message to other programmers; the double underscore exists mainly to stop a subclass from accidentally overwriting an attribute of the same name.
Comparing objects
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
a = Point(1, 2)
b = Point(1, 2)
print(a == b) # False - two different objects
print(a is b) # False
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self): # needed to stay usable in sets
return hash((self.x, self.y))
print(Point(1, 2) == Point(1, 2)) # True
print(len({Point(1, 2), Point(1, 2)})) # 1Defining__eq__sets__hash__toNone, making instances unhashable. If your objects should work in sets or as dictionary keys, define__hash__as well - and only over attributes that never change.
Objects holding objects
class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
def start(self):
return f"engine with {self.horsepower}hp started"
class Car:
def __init__(self, model, horsepower):
self.model = model
self.engine = Engine(horsepower) # a Car HAS an Engine
def start(self):
return f"{self.model}: {self.engine.start()}"
print(Car("Sedan", 120).start())This is composition: building behaviour by holding other objects. It is usually a better first choice than inheritance, and the advanced OOP note explains why.
A worked example
class Library:
def __init__(self, name):
self.name = name
self.books = {}
self.borrowed = {}
def add_book(self, isbn, title, copies=1):
if isbn in self.books:
self.books[isbn]["copies"] += copies
else:
self.books[isbn] = {"title": title, "copies": copies}
return self
def available(self, isbn):
if isbn not in self.books:
return 0
return self.books[isbn]["copies"] - len(self.borrowed.get(isbn, []))
def borrow(self, isbn, member):
if self.available(isbn) <= 0:
raise ValueError(f"no copies of {isbn} available")
self.borrowed.setdefault(isbn, []).append(member)
return self.books[isbn]["title"]
def return_book(self, isbn, member):
holders = self.borrowed.get(isbn, [])
if member not in holders:
raise ValueError(f"{member} does not hold {isbn}")
holders.remove(member)
def report(self):
for isbn, details in sorted(self.books.items()):
free = self.available(isbn)
print(f"{details['title']:<24}{free}/{details['copies']} available")
library = Library("City Library")
library.add_book("978-1", "Dune", 2).add_book("978-2", "Emma")
library.borrow("978-1", "meera")
library.borrow("978-1", "arun")
library.report()
try:
library.borrow("978-1", "sara")
except ValueError as error:
print("error:", error)Common mistakes
- Omitting
selffrom a method definition. - Omitting
self.when assigning an attribute. - Calling another method without
self.. - Passing
selfexplicitly at the call site:obj.method(obj). - Defining
__eq__without__hash__and then using the object in a set. - Assuming a double underscore makes an attribute private.
Best practices
- Always name the first parameter
self. - Reach every attribute and method through
self. - Mark internals with a single leading underscore.
- Return
selffrom mutating methods only when chaining genuinely reads better. - Validate in the method that changes state, so an object cannot enter an invalid state.
Practice
- Write a
Stackclass with push, pop, peek and is_empty, raising on an empty pop. - Write a class whose methods chain, and build an object in one expression.
- Show the exact error for a method missing
self, and explain the message. - Give a class value based equality and prove two equal instances collapse in a set.
- Model a
Playlistthat holdsSongobjects and reports total duration.
Conclusion
obj.method(args) is Class.method(obj, args). Once that substitution is automatic in your head, self stops being mysterious and the two classic errors - a missing self parameter and a missing self. prefix - become instantly readable.