Card 06/ 09

ComparisonDifficulty: Intermediate2 min

Observer Classes vs Callbacks in Python

A list of functions is a subject; append is attach.

In Python an observer with one method can be a plain function, a callback, and the subject can be a list of them. Appending to the list attaches, removing detaches, and calling each in turn notifies. This card puts that form beside the class form, and sets out what each loses.

py
from collections.abc import Callable

Listener = Callable[[int], None]


class Thermostat:
    def __init__(self) -> None:
        self.listeners: list[Listener] = []

    def set_temperature(self, celsius: int) -> None:
        for listener in self.listeners:
            listener(celsius)


def show(celsius: int) -> None:
    print(f"display: {celsius} C")


thermostat = Thermostat()
thermostat.listeners.append(show)
thermostat.listeners.append(lambda celsius: print("heating on") if celsius < 18 else None)
thermostat.set_temperature(21)
thermostat.set_temperature(16)
OutputRan with python:3.12-slim (Python 3.12.13)
display: 21 C
display: 16 C
heating on
A named function and a lambda subscribe to the same thermostat.

The thermostat has no Observer interface and no observer classes. show and a lambda subscribe by being appended, and each reading reaches both: the display shows 21 and then 16, and the heating line appears only for the reading below 18.

The two forms compared
ConcernObserver classesCallbacks
Code per observerA class with an update methodA function or a lambda
State between notificationsFields on the observerA closure, or a bound method of an object that holds it
Several kinds of eventSeveral methods on one interface, such as order_placed and order_cancelledOne list per event
Detachingdetach(observer) with the object you attachedRemove the same function object; a lambda kept nowhere cannot be removed
What the type saysThe interface names the eventsCallable[[int], None] says only the arguments

The fourth row causes the most trouble in practice. A lambda passed straight to append exists only in the list, so nothing else holds it and nothing can name it to remove it later. Keep a reference to any callback that will need detaching, or pass a named function.

Use callbacks when each subscriber reacts to one event and needs no state of its own, which covers most user interface handlers and hooks. Use observer classes when the subject has several related events, and the interface that names them together is worth having.