A class gives a name to a useful combination of state and behavior. Calling
LabelledShelf("front") creates an instance; its __init__ method establishes
that instance’s starting attributes. The class object and each instance are
different objects. Two shelves can share the same method definitions while
keeping different names and different item lists.
Start with a responsibility: a shelf remembers a name and an ordered sequence of item labels. A dictionary can hold data, but a class lets the public operations and the rules about that data live together. A class is useful here because we will add items over time and want every observation to follow one interface. A one-off calculation with no continuing state can remain an ordinary function.
Calling an instance method
self is the conventional first parameter of an instance method. Python binds
an instance when you access its method: front.add("map") passes front as
self. In this example, Shelf.add(front, "map") is the equivalent explicit
call. The parameter name is a convention, while the binding behavior comes from
accessing the function through an instance.
class Shelf:
kind = "shelf"
def __init__(self, name, items=()):
self.name = name
self._items = list(items)
def add(self, item):
self._items.append(item)
def snapshot(self):
return tuple(self._items)
front = Shelf("front", ["map"])
back = Shelf("back")
front.add("pen")
Shelf.add(back, "ticket")
print(front.name, front.snapshot())
print(back.name, back.snapshot())
print(Shelf.kind)The class attribute kind is an intentionally shared description. Assigning
self.name and self._items creates instance attributes. A mutable list placed
at class level would instead be shared by instances that look it up there.
Mutating that shared list through one shelf could affect every other shelf.
Creating a new list inside __init__ makes the intended ownership visible.
Decide what the constructor owns
Even an instance attribute can point to borrowed storage. Assigning
self._items = items would keep the caller’s list, so later caller edits would
change the shelf. list(items) copies the outer container. This task stores
strings, so sharing those immutable elements is safe; a shallow copy would
need more thought if the elements themselves were mutable entities.
A tuple snapshot copies the current sequence of references into immutable
observation data. Adding to the shelf later cannot add to an old tuple. This
is a structural snapshot, not a general deep-freeze operation. The underscore
in _items marks an internal detail by convention; it does not prevent another
Python program from reaching that attribute.
Before the build, trace two empty shelves, two shelves initialized from the
same list, and an old snapshot observed after an addition. Predict which values
should change. The checks practice method binding and shared state; the bug
hunts break ownership and instance naming; the fill establishes owned storage.
Then implement LabelledShelf against the exact public build contract.