26. Methods and self

You have called methods on objects many times — "hello".upper(), my_list.append(x), math.floor(n). This chapter shows how to define your own methods that belong to a particular object, and what self is. Understanding self once explains a lot of Python code.

Functions on objects, the long way

In Python, a method is just a function defined inside a class. But before classes, think about a plain dictionary holding a counter:

counter = {"count": 0}

def step(c):
    c["count"] = c["count"] + 1

step(counter)
step(counter)
step(counter)
print(counter["count"])   # 3

The function step takes the dictionary itself as its first argument, then uses it to update the count. Every call has to pass counter explicitly: step(counter). This works but reads awkwardly.

Methods and self in a class

Python's class syntax solves this. A method is a function defined inside a class body. When you call obj.method(), Python automatically passes obj as the first argument. By convention that first parameter is named self:

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

    def step(self):
        self.count = self.count + 1

counter = Counter()
counter.step()
counter.step()
counter.step()
print(counter.count)   # 3

counter.step() is exactly the same as calling Counter.step(counter) — Python fills in self automatically from the object before the dot.

Inside the body, self is a normal parameter. Write self.x to read a field, self.x = ... to change one. The name self is a strong convention in Python; every method should use it.

Open exercises/26/01-counter.py. It already has Counter with a step method. Add a reset method that sets self.count back to 0, and call it from the bottom of the file.

Built-in method calls

Python's built-in types use the same mechanism. A string is an object; calling s.upper() passes s as self to the string's upper method:

name = "keiko"

print(name.upper())    # KEIKO
print(len(name))       # 5

Every method you call on a list, string, or dictionary works this way. Your own classes behave identically.

A small game object

Method syntax shines when several pieces of state go together. Here is a dog with a name and a number of barks left:

class Dog:
    def __init__(self, name, barks_left):
        self.name = name
        self.barks_left = barks_left

    def bark(self):
        if self.barks_left <= 0:
            print(f"{self.name} is hoarse.")
            return
        print(f"{self.name}: Woof!")
        self.barks_left = self.barks_left - 1

dog = Dog("Rex", 3)
dog.bark()       # Rex: Woof!
dog.bark()       # Rex: Woof!
dog.bark()       # Rex: Woof!
dog.bark()       # Rex is hoarse.

The Dog object holds the state; the method changes it on every call. This is the core of object orientation: an object with data and the methods that act on it.

Common mistake: forgetting self in the definition

If you forget self as the first parameter, Python still passes the object, but your method receives it in the wrong slot:

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

    def step():         # missing self
        self.count += 1

c = Counter()
c.step()   # TypeError: step() takes 0 positional arguments but 1 was given

The fix is always to add self as the first parameter of every method.

Homework

Problem 1 — Counter object

Open exercises/26/homework/01-counter-object.py. Build a Counter class with the fields and methods:

  • self.count — starts at 0.
  • inc() — adds 1.
  • get() — returns the current count.
  • reset() — sets count back to 0.

Call them in sequence and print the value at each step.

Problem 2 — Dog with bark

Open exercises/26/homework/02-dog.py. Build a Dog class with a name field and a bark() method that prints <name>: Woof!. Make a second dog with a different name. Call both bark() and confirm each prints its own name.

Problem 3 — Calculator object

Open exercises/26/homework/03-calculator.py. Build a Calc class with:

  • self.value — starts at 0.
  • add(n) — adds n to self.value.
  • sub(n) — subtracts.
  • mul(n) — multiplies.
  • show() — prints the current value with a label.

Chain a few calls and confirm the value updates as expected.

Challenge — Stack

Open exercises/26/homework/04-stack.py. Build a Stack class that behaves like a stack:

  • self.items — a list.
  • push(v) — adds v to the top.
  • pop() — removes and returns the top.
  • peek() — returns the top without removing it (or None if empty).
  • size() — returns how many items are stored.

Use self.items.append(v) and self.items.pop() without an index — that pushes and pops at the end of the list, the standard stack behaviour.

Stuck or finished? Open the homework solutions page.