Skillversitas

Methods and the self Parameter

A method is a function defined in a class body that operates on an instance, and self is the instance - passed to the method automatically when you call it as an attribute. The automatic part is the magic that confuses everyone first: you never write the instance in the call (acc.deposit(50), not deposit(acc, 50)), yet the method receives it as its first argument. Understanding the binding - how acc.deposit becomes a function with acc pre-filled - makes self ordinary instead of mysterious, and it is the key to calling methods explicitly, passing them around, and writing the unbound patterns the advanced chapters rely on.

What the Call Actually Does

class Counter:
    def __init__(self):
        self.count = 0

    def increment(self, by=1):
        self.count += by
        return self.count

c = Counter()
print(c.increment())       # 1
print(c.increment(10))     # 11

# under the hood: c.increment is a BOUND method - increment with c pre-filled
bound = c.increment
print(bound(5))            # 16 - calling the bound method directly

# the unbound function is on the class; call it by passing the instance yourself
print(Counter.increment(c, 2))   # 18 - explicit self

Accessing a method through an instance (c.increment) produces a bound method: the original function with the instance glued onto it as the first argument. That is why the definition needs self and the call does not. You can call the bound method as a plain callable (it is a first-class object - assign it, pass it as a callback), and you can bypass binding entirely by calling the function from the class and passing the instance by hand. The explicit form is rare in application code but appears in descriptors, __get__ implementations, and metaclass hooks - you will recognize it when you meet it.

The self Convention

By universal convention the first parameter is named self. Python does not require the name - any identifier works - but the tooling, the linters, and every reader expect self, so deviating costs more than it saves. The three rules: it must be the first parameter, it is the instance being operated on, and you pass it exactly as many times as the call supplies instances (which is always once, via binding). Forgetting it in the definition is the most common first-day TypeError, and the message ("missing 1 required positional argument") is the tell.

Method Patterns

class Rect:
    def __init__(self, w, h):
        self.w = w
        self.h = h

    # a pure query: reads state, returns data, changes nothing
    def area(self):
        return self.w * self.h

    # a mutator: changes state, returns None (the Python convention)
    def scale(self, factor):
        self.w *= factor
        self.h *= factor

    # a fluent mutator: returns self to allow chaining (a deliberate choice)
    def translate(self, dx, dy):
        self.w += dx
        self.h += dy
        return self

    # a method that takes OTHER objects as data - composition in action
    def union(self, other):
        return Rect(max(self.w, other.w), max(self.h, other.h))

r = Rect(4, 5).translate(1, 1)
print(r.area())            # 30
s = r.union(Rect(10, 2))
print(s.w, s.h)            # 10 6

Three shapes cover most method design. Queries return data and change nothing - name them for what they give (area, is_empty). Mutators change state and return nothing by default (the convention that None means "in place") - name them verbs. Fluent builders return self to enable chaining, a deliberate API choice you must document because it breaks the mutator convention. Note the last method: methods take other instances as ordinary arguments, which is how composition and interaction between objects is expressed - union knows only that other has .w and .h, the duck-typing the overview promised.

Instance, Class, Static: The Preview

Methods come in three flavors by what they receive: instance methods get self (the default, covered here), class methods get cls (the class - for factories that should be overridable, chapter 13), and static methods get nothing extra (for helpers that happen to live in the class's namespace). The test for which you need: does it need the instance? self. Does it need the class (to construct the right subtype)? @classmethod. Neither? A plain function, possibly @staticmethod if it must live with the class. Choosing correctly is an API-clarity decision, not just a technical one.

Summary

self is the instance, passed automatically because obj.method creates a bound method - the function with the instance pre-filled as its first argument. You can call bound methods directly or unbind by calling Class.method(instance, ...) explicitly. Follow the naming convention, keep self first, and design methods in the three shapes: queries that return data, mutators that change state and return None, and the deliberate fluent variant that returns self. Methods take other instances as ordinary duck-typed arguments - that is how objects collaborate. The instance/class/static trichotomy (chapter 13) decides what a method receives; the binding explained here is why the instance form works at all.