main
No variables
A single item can manage borrowing and returning itself. A library needs to find items, prevent duplicate identifiers, and coordinate operations across a collection.
Those are different responsibilities. One object can use other objects without taking over every detail of their behavior.
Using LoanItem from the previous chapter:
items = [ LoanItem("E10", "Camera", "media"), LoanItem("E20", "Tent", "outdoors")]
for item in items: print(item.name)The loop receives one instance at a time. List iteration did not change because the values became custom objects.
A returned instance is also an ordinary object reference. If a search returns an item from this list, a caller’s method call on that item affects the same object the list contains.
Add this class after the existing LoanItem definition:
class EquipmentLibrary: def __init__(self): self._items = []
def find(self, item_id): for item in self._items: if item.item_id == item_id: return item
return None_items begins with an underscore to mark it as an implementation detail. The convention asks callers to use the library’s operations rather than modify its list directly. Python does not enforce that convention as an access restriction.
The list is created inside __init__, so each library instance gets its own collection.
None Means the Search Found No ItemThe search returns either an existing instance or None. The tests use is None and is not None to ask whether the result is that specific absence marker.
is compares object identity, not equal contents. Use this idiom for None; do not replace ordinary string or numeric equality tests with identity comparisons.
The find contract uses an exact, already-trimmed identifier. User input can be stripped by the user interface before calling it. The library does not guess that "e10" and "E10" mean the same identifier.
Add this method inside EquipmentLibrary, alongside find:
def add_item(self, item_id, name, category): item = LoanItem(item_id, name, category)
if self.find(item.item_id) is not None: raise ValueError("The item identifier already exists.")
self._items.append(item) return itemLoanItem checks and trims the text. The library can then check the identifier against the whole collection. It appends the new object only after both checks succeed.
Next, add the borrowing operation inside the same class:
def borrow_item(self, item_id): item = self.find(item_id)
if item is None: return False
return item.borrow()borrow_item finds the requested object. It does not duplicate the availability decision:
return item.borrow()This is delegation: one object asks another object’s method to do part of the work. The item “owns” the borrowing rule in the sense that it is responsible for checking and applying it. Other parts of the program can still hold references to that item. If the rule changes later, the library need not contain a second copy to update.
The complete example below brings the two classes together. Predict which object changes when library.borrow_item("E10") runs, then run the assertions. A successful run prints Checks passed..
The identity assertion checks that searching returns the existing item, not a separately constructed replacement. The diagram below shows which objects the names refer to during the call to LoanItem.borrow.
The library’s list and the names camera, item, and self refer to the same loan-item instance.
Only one loan-item instance appears in the diagram. When borrow changes self.available, the change is visible through camera and through the library’s list. In EquipmentLibrary.borrow_item, self refers to the library; in LoanItem.borrow, it refers to the item receiving that call.
Before stepping through the calls, predict the two printed values. Which instance does self refer to in each method?
This reduced example keeps only the item’s identifier and availability. To keep setup short, its library initializer accepts an existing item; the complete example above uses add_item and validates the fields.
Code
Step 1 of 18
State
No variables
Objects
No objects yet
Output
No output yet
Pause when LoanItem.borrow changes available. The two active methods each have a local self, but those names refer to different objects. Follow the item’s True result back through borrow_item to the caller: the library coordinates the operation, while the item decides whether borrowing succeeds.
Change the identifier in the borrow assertion to "missing". Predict which assertion fails and why the camera stays available. Restore "E10" afterward.
The next exercise supplies these definitions and asks you to add the matching return operation.
0 / 15 points
Complete EquipmentLibrary.return_item(item_id) in the supplied classes. For an exact identifier that exists, call that item’s return_item() and return its boolean result. For a missing identifier, return False without changing anything. Do not duplicate the item’s transition rule in the library.
Identifiers are case-sensitive and already trimmed by the caller. Do not print or read input. Keep the supplied addition, search, and borrowing behavior. Check repeated returns and references to the original item as well as a successful return.
Work in main.py. Open Show file tree, select check.py, and Run to try the supplied sample checks. Add cases for the remaining requirements, including boundary values and repeated operations where relevant. Keep test calls in check.py so importing main.py does not print or ask for input. Submit sends the files for grading against the full contract and returns feedback.
The list-building pattern from Part 2 also works with objects: start a new result list, inspect each object, and append a selected value when it meets the condition. Reading item.name or item.available does not change the item.
Decide what the caller should receive. A search returns the existing item instance, so calling its methods can change the item in the library. A list of names can instead be a separate list of strings. The next exercise asks for that second kind of result.
0 / 15 points
Complete EquipmentLibrary.available_names() in the supplied classes. Return a new list containing the names of all currently available items, in insertion order. Different items may share a name; keep both occurrences. Do not print or change the library or its items.
An empty library or one with no available items returns []. Changing the returned list must not change the library. Use the supplied add_item, find, and borrowing methods in your checks.
Work in main.py. Open Show file tree, select check.py, and Run to try the supplied sample checks. Add cases for the remaining requirements, including boundary values and repeated operations where relevant. Keep test calls in check.py so importing main.py does not print or ask for input. Submit sends the files for grading against the full contract and returns feedback.
An EquipmentLibrary has a collection of LoanItem instances. This relationship is called composition: one object is built using other objects.
The library keeps the collection and coordinates operations. Each item keeps its own availability and decides whether borrowing succeeds. Containing an object lets the library use that object’s methods without copying its rules.
Use the same relationship in a study log: the log stores session objects and calculates a total from their current durations.
0 / 25 points
Complete ReadingLog using the supplied StudySession class. Each log starts with an independent empty sessions list.
add_session(subject, minutes) receives a subject string and positive integer minutes. Preserve the subject exactly, including whitespace or an empty string. Create and append a new StudySession, then return that instance. Repeated subjects are allowed.total_minutes() returns the total of the durations currently stored in its sessions, or 0 for an empty log.Do not print or read input. A caller may change the minutes on a returned session; subsequent totals must reflect that change. Separate logs must remain independent.
Work in main.py. Open Show file tree, select check.py, and Run to try the supplied sample checks. Add cases for the remaining requirements, including boundary values and repeated operations where relevant. Keep test calls in check.py so importing main.py does not print or ask for input. Submit sends the files for grading against the full contract and returns feedback.
find return an existing instance rather than a replacement?_items communicate, and what does it not enforce?__init__?