Skip to main content

Methods and state

A method is a function stored in the class and called through an instance: a.add(55) means RunningMean.add(a, 55). Build a running mean that keeps its total and count between calls, and meet the list that every instance shares by mistake.
P01.9.2L0 Foundation25 minIn review

Before this lesson

P01.9.1 Classes and instances

After this lesson you can

  • Write methods that read and change an instance’s attributes through self.
  • Rewrite a call a.add(55) as RunningMean.add(a, 55), and explain how Python finds the method.
  • Tell a method that changes the object from one that only answers, and trace an object’s state call by call.
  • Explain why a list in the class body is shared by every instance, and fix it in __init__.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

Scores arrive one at a time: from a sensor, from a web form, or from a file too large to hold in memory. You want their mean at any moment, without keeping every score. Two numbers are enough, the total so far and the count so far, but they must survive from one score to the next, and a function forgets its local names when it returns (P01.4.3). Where can those two numbers live, and where should the code that updates them go?

From the last lesson

1. What is self inside __init__?

Show answer

The new instance that is being filled in. Python passes it as the first argument when the class is called.

2. After ben.score = 70, why is ama.score still 55?

Show answer

ama and ben are two objects, each with its own table. The assignment rebinds score in Ben’s table only.

Intuition

An object’s attributes last as long as the object does, so they are the place for numbers that must survive between calls. The values of an object’s attributes at a given moment are its state. The functions that change and read that state are written inside the class, where they are called methods.

At the top: a.add(55) means RunningMean.add(a, 55). Below, a memory diagram. The global names a and b point to two RunningMean objects: a's table holds total 120 and count 2, b's holds total 80 and count 1. Both have dashed arrows to one class box, RunningMean, which holds __init__, add and mean, each a function; add is on a yellow band. A green arrow runs from a's object to add in the class. A note says Python looks in a's own table first, does not find add, and finds it in a's class.
P01.9.2-F1

The data lives in each instance; the methods live once, in the class. a.add(55) finds add in a’s class and calls it with a as self.

The picture holds the two ideas of this lesson.

  1. Each instance has its own state. a has seen two scores and b one, and neither knows about the other.
  2. There is one copy of each method. add is not stored in a or in b. When you write a.add(55), Python looks for add in a’s table, does not find it, looks in a’s class, finds the function there, and calls it with a as the first argument.

Build it up

Step 1. A function that updates an object. Start with the class of the last lesson’s shape: __init__ gives each new object a total and a count of 0. A plain function can then update any such object passed to it.

class RunningMean:
    def __init__(self):
        self.total = 0
        self.count = 0


def add(running, value):
    running.total += value
    running.count += 1


a = RunningMean()
add(a, 55)
add(a, 65)
print(a.total, a.count)
120 2

The numbers survive between the calls because they live in the object, not in the function’s frame.

Step 2. Move the function into the class. Indent it under the class and it becomes a method. By convention its first parameter is renamed self: the object the method works on.

class RunningMean:
    def __init__(self):
        self.total = 0
        self.count = 0

    def add(self, value):
        self.total += value
        self.count += 1

    def mean(self):
        return self.total / self.count

Step 3. Two ways to call it. A method is a function stored in the class, so it can be called through the class, with the object passed by hand. The short form calls it through the object, and Python passes the object for you.

a = RunningMean()
RunningMean.add(a, 55)        # the long form: the object passed by hand
a.add(65)                     # the short form: Python passes a as self
print(a.total, a.count, a.mean())
120 2 60.0
Top: two code boxes, a.add(65) and RunningMean.add(a, 65), joined by the word means. Below, the method header def add(self, value): with arrows from a to self and from 65 to value. Bottom: the call stack during the call, a global frame holding a, and a frame for add, running line 7, holding self and value 65. Both a and self point to the same RunningMean object, whose table holds total 55 and count 1, with total on a yellow band because line 7 is about to change it.
P01.9.2-F2

a.add(65) is RunningMean.add(a, 65): the object before the dot becomes self, and the arguments in the brackets fill the other parameters.

That is all that self is: the first parameter, which receives the object before the dot. The Python Tutorial puts it the same way: “the call x.f() is exactly equivalent to MyClass.f(x)” (9.3.4).

Step 4. Each instance keeps its own state. Make a second object and give it a different score. The one add updates whichever object it is called through.

b = RunningMean()
b.add(80)
print(a.mean(), b.mean())
60.0 80.0

The formal statement

NoteDefinitions: method, state

A method is a function defined in a class body. Called through an instance, as obj.m(args), it receives that instance as its first parameter, named self by convention. The state of an object is the values of its attributes at a given moment.

Looking up an attribute, obj.name, in two places:

  1. the instance’s own table, vars(obj);
  2. if the name is not there, the class of the instance.

If both fail, Python raises AttributeError. The Tutorial (9.4): “If the same attribute name occurs in both an instance and in a class, then attribute lookup prioritizes the instance.”

Calling a method. When the lookup finds a function in the class, obj.m gives a bound method: the function with obj attached. Calling it puts obj in front of the arguments:

\[ \begin{aligned} &\texttt{a.add(65)}\\ &\quad= \texttt{RunningMean.add(a, 65)} \end{aligned} \]

Two kinds of method. add changes the object’s state and returns None. mean only reads the state and returns an answer, so calling it twice gives the same value. Keeping the two kinds apart makes a program easy to trace: only the calls that change state can make a later answer different.

Two columns. Left, add changes the object: a's table before the call a.add(80) holds total 120 and count 2, after it total 200 and count 3, and the call returns None. Right, mean only answers: a's table before and after a.mean() is the same, total 120 and count 2, and the call returns 60.0. A line below says a model works the same way: fit changes it, predict only answers.
P01.9.2-F5

A method that changes the object, beside a method that only answers. A fitted model has both kinds: fit changes it, predict only answers.

This is how a model in machine learning keeps what it has learned. Calling fit on a scikit-learn model changes its state: it stores the learned numbers in attributes whose names end in an underscore, such as coef_. Calling predict reads them and answers. P01.9.4 builds two such models.

Worked example

Feed the five scores of the study table into one RunningMean and trace its state after every call. Predict each mean by hand first.

scores = [55, 65, 80, 40, 60]
running = RunningMean()
for score in scores:
    running.add(score)
    print('after', score, 'total', running.total, 'count', running.count, 'mean', running.mean())
after 55 total 55 count 1 mean 55.0
after 65 total 120 count 2 mean 60.0
after 80 total 200 count 3 mean 66.66666666666667
after 40 total 240 count 4 mean 60.0
after 60 total 300 count 5 mean 60.0
The listing of RunningMean, eleven lines, above a trace table with one row per call. running.add(55): total 55, count 1, mean 55.0, by hand 55 / 1. add(65): 120, 2, 60.0, by hand 120 / 2. add(80): 200, 3, 66.666..., by hand 200 / 3. add(40): 240, 4, 60.0, by hand 240 / 4. add(60): 300, 5, 60.0, by hand 300 / 5. The first and last 60.0 rows are on a yellow band.
P01.9.2-F3

The trace of the worked example. Only total and count are kept; each mean is worked out from them when it is asked for.

The trace shows three things.

  • After 55 and 65 the mean is \({120 / 2} = 60.0\), and after all five scores it is \({300 / 5} = 60.0\) again, the mean of the study table (P01.3.4).
  • In between it moves: after 80 it is \({200 / 3}\), printed as 66.66666666666667 because a float keeps about 16 digits (P01.2.1).
  • At no point is a list of scores kept. However many scores arrive, the object holds two numbers.

Code

The library version is statistics.mean, which needs the whole list at once. The running mean gives the same answer while holding only two numbers, so it can read a file one line at a time.

import pathlib
import statistics
import tempfile

with tempfile.TemporaryDirectory() as folder:
    path = pathlib.Path(folder, 'study.csv')
    path.write_text('name,hours,score\nAma,2,55\nBen,4,65\nChen,5,80\nDina,1,40\nEli,3,60\n',
                    encoding='utf-8')
    from_file = RunningMean()
    with open(path, encoding='utf-8') as file:
        for line in file:
            if line.startswith('name,'):            # the header line
                continue
            name, hours, score = line.strip().split(',')
            from_file.add(int(score))

print(from_file.mean(), statistics.mean(scores))
60.0 60

The two agree: 60.0 from the class, and 60 from statistics.mean, which keeps whole numbers whole when it can: the scores are whole and their mean comes out exact (P01.5.1). Only one line of the file is in memory at a time, and the object holds two numbers however long the file is.

Methods are ordinary functions stored in the class, and a bound method remembers its object:

bound = a.add
print(bound.__self__ is a, bound.__func__ is RunningMean.add)
bound(5)                                            # the same as RunningMean.add(a, 5)
print(a.total, a.count)
True True
125 3

Explore

P01.9-X1

Instance workbench

Choose a score and press a.add or b.add: only that instance’s total and count change, while the methods stay in the class. Then turn on the switch that keeps the scores in a list in the class body, and add scores to a and b again. Whose list grows?

A memory diagram: names a and b point to two RunningMean objects, a with total 120 and count 2, b with total 80 and count 1, each with a dashed arrow to the one class RunningMean, which holds the methods __init__, add and mean.

Pitfalls

Two rows. Wrong, with a cross: a class ListMean with values = [] in the class body. After a.add(55), a.add(65) and b.add(80), the two instances a and b have empty tables, and both have dashed arrows to the class, whose one name values points to the single list [55, 65, 80]. Both a.mean() and b.mean() give 66.666... Right, with a tick: values = [] moved into __init__ as self.values = []. Now a's table has values pointing to [55, 65] and b's has values pointing to [80], and a.mean() is 60.0, b.mean() is 80.0.
P01.9.2-F4

Wrong beside right. A list written in the class body exists once, in the class, and every instance finds it there. A list made in __init__ is made once for each instance.

“A list in the class body gives each instance its own list.”

The class body runs once, when the class is made, so values = [] there makes one list, stored in the class. self.values.append(...) looks up values, does not find it in the instance, finds the class’s list, and changes it. Every instance shares it. The Python Tutorial (9.3.5) shows the same bug with a list of a dog’s tricks that is “unexpectedly shared by all dogs”. Make the list in __init__, with self.values = [].

Go deeper: why a number in the class body does not cause the same bug

If the class body said total = 0 and add did self.total += value, the instances would not share a total. self.total += value means self.total = self.total + value: the right side reads the class’s 0, but the assignment then binds total in the instance’s own table. From then on the instance’s own name hides the class’s. self.values.append(value) never assigns to self.values; it changes the one list it finds, which is the class’s.

“print(a.mean) prints the mean.”

Without brackets nothing is called: a.mean is the bound method itself, and print shows something like <bound method RunningMean.mean of ...>. Call it: a.mean().

“A method does not need self if it uses no attributes.”

Python passes the object anyway. A method written def mean(): stops with TypeError when called as a.mean(): RunningMean.mean() takes 0 positional arguments but 1 was given (Python 3.15; the wording varies a little between versions).

“mean() of a new RunningMean gives 0.”

With nothing added, self.total / self.count is \({0 / 0}\), and Python raises ZeroDivisionError. A method should state its rule: check if self.count == 0: and raise a ValueError with a clear message (P01.8.2), as exercise 3 does.

Check yourself

Recall

1. Rewrite b.add(80) without the short form.

Show answer

RunningMean.add(b, 80).

2. Where does Python look when it evaluates a.add, and in what order?

Show answer

First in the instance’s own table, vars(a); then, if it is not there, in a’s class, where it finds the function add.

3. After a.add(55) and a.add(65), what are a.total, a.count and a.mean()?

Show answer

120, 2 and \({120 / 2} = 60.0\).

4. Which method of RunningMean changes the object, and which only answers?

Show answer

add changes the state (total and count) and returns None; mean only reads them and returns the mean.

5. Why do all instances share a list written in the class body?

Show answer

The class body runs once, so it makes one list, stored in the class. Instances have no values of their own, so self.values finds the class’s list.

Exercises

1. ★ Give RunningMean a method reset(self) that sets the total and the count back to 0. Is it a method that changes the object or one that only answers?

Show solution
    def reset(self):
        self.total = 0
        self.count = 0

Written inside the class, under mean. It changes the object’s state (and returns None), so it is of the same kind as add.

2. ★★ The mean squared error of predict(hours) = 9 * hours + 33 on the study table is a mean of squared errors (P01.6.2). Compute it with a RunningMean, without building a list. Work out the answer by hand first.

Show solution

The predictions are 51, 69, 78, 42 and 60, the errors 4, -4, 2, -2 and 0, and their squares 16, 16, 4, 4 and 0, which add up to 40. So the mean squared error is \({40 / 5} = 8.0\).

def predict(hours):
    return 9 * hours + 33


squared = RunningMean()
for h, s in zip([2, 4, 5, 1, 3], [55, 65, 80, 40, 60]):
    squared.add((s - predict(h)) ** 2)
print(squared.mean())          # 8.0

A training loop reports its loss in exactly this way: a running mean over the batches seen so far.

3. ★★★ Change mean so that, with no values added, it raises ValueError('mean of no values') instead of ZeroDivisionError. Then write a test (P01.8.4) that checks both a usual case and the error case.

Show solution
    def mean(self):
        if self.count == 0:
            raise ValueError('mean of no values')
        return self.total / self.count
import pytest


def test_running_mean():
    running = RunningMean()
    running.add(55)
    running.add(65)
    assert running.mean() == 60.0


def test_running_mean_of_nothing_is_an_error():
    with pytest.raises(ValueError):
        RunningMean().mean()

The first test builds the state it needs with two calls to add, then checks the answer against \({(55 + 65) / 2} = 60.0\), worked out by hand.

Summary card

Data lives in each instance; methods live once, in the class.

The anchor figure again: a and b point to two RunningMean objects with their own total and count, both linked to one class that holds __init__, add and mean.
P01.9.2-F1

One class, two states.

  • Method: def add(self, value): inside the class body.
  • Call: a.add(55) is RunningMean.add(a, 55); self is the object before the dot.
  • Lookup: the instance’s table first, then its class.
  • State: attributes that last between calls; add changes them, mean only reads them.
  • Bug: a list in the class body is shared; make it in __init__.

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, The Python Tutorial, chapter 9, Classes: 9.3.4, that “the call x.f() is exactly equivalent to MyClass.f(x)”; 9.3.5, the Dog class whose tricks list is “unexpectedly shared by all dogs”, fixed with self.tricks = [] in __init__; and 9.4, that “attribute lookup prioritizes the instance”.
  • scikit-learn developers, Developing scikit-learn estimators (version 1.9.1): every estimator implements fit, which returns self, and attributes “estimated or learned from the data must always have a name ending with trailing underscore, for example … coef_”.
  • Ana Bell, MIT OpenCourseWare 6.100L, Fall 2022, lecture notes 17 and 18: lecture 17 sets the “conventional way”, c.distance(zero), beside its equivalent, Coordinate.distance(c, zero); lecture 18 says that a class “defines data and methods common across all instances”, while data attribute values vary between instances.

Next. P01.9.3 gives methods with fixed names, which print, ==, len and + call for you.

This lesson unlocks

P01.9.3 Special methods: objects that act like built-ins

Your progress is saved in this browser only. No account, no tracking.
Back to top