The final project
Picture a storekeeper with a notebook. Each line of the notebook holds an item name and how much of it is on the shelves. Apples arrive, and the storekeeper adds them to the "apples" line. Someone asks for more than there is, and the storekeeper refuses and changes nothing in the notebook.
A class in a real program usually rests on several ideas at once. For example, it keeps data in a dict, changes it through methods and raises an exception when an operation is impossible. The final exercise works the same way: it introduces no new constructs; you combine familiar ones.
After it, the course is over, and the next step is the Python interview simulator. The "Practice a Python interview" button on the course completion page opens it. The simulator asks about the course's topics and about those the course did not touch.
The warehouse
The exercise is a warehouse that counts the units of each item. The storekeeper's notebook here is a dict in the class Inventory:
- the key is an item name;
- the value is a number of units.
If an item cannot be taken, the take method raises the OutOfStockError exception and leaves the warehouse unchanged. The class OutOfStockError is already declared in the stub, and the tests catch only this class: with another exception, the test fails.
| Part of the exercise | What it needs |
|---|---|
__init__ | an instance attribute holding a dict |
add and count | get with a default value |
take | a comparison, raise, del |
names | sorted |
Three outcomes of take
The trickiest method is take: it has three outcomes, and the tests check each one.
- There is less of the item than asked for, or none at all:
takeraisesOutOfStockError, and the quantity does not change. - Exactly the amount on hand is taken: the item leaves the dict, and
namesno longer returns it. - There is more of the item than asked for: the quantity goes down by
qty.
This is where the notebook analogy stops. A storekeeper may keep the line "apples: 0." Our warehouse has no such line: an item taken out in full is removed from the dict.
How to proceed
Work step by step:
- Start with
__init__: without the dict, the other methods have nothing to work with. - Then write one method at a time and run the tests after each.
- Almost every test calls
addfirst, so a bug inaddfails many rows at once. Fix it first.
The stub's methods return None, so at first none of the eight tests passes. The row test_add_then_count shows got None, and a test that expects an exception shows no OutOfStockError, got None.
Common mistake
The dict is declared as a class attribute, not an instance one:
class Inventory:
items = {} # ✗
def add(self, name, qty):
self.items[name] = self.items.get(name, 0) + qty
def count(self, name):
return self.items.get(name, 0)
shop = Inventory()
home = Inventory()
shop.add("apple", 5)
print(home.count("apple")) # 5There is no error, but the warehouse home sees the apples put into shop. Let's see why:
- The line
items = {}in the class body creates one dict, a class attribute. - The instance
shophas noitemsattribute of its own, soself.itemsfinds the class's dict. - The line
self.items[name] = …assigns to a key of that dict, not to an attribute of the instance. home.count("apple")reads the same dict and prints5.
A class attribute is one for all instances, and both warehouses write in one notebook. Each test creates its own warehouse, but with this attribute it gets the items from the earlier tests. Create the dict in __init__: self.items = {}.
Interviewers like to ask how a class attribute differs from an instance attribute. The answer: a class attribute is one for all instances, while each instance has its own attribute, created through self in __init__.
Key points
- The warehouse keeps the quantities in a dict: the key is the item, the value a number of units.
- The dict is created in
__init__asself.items = {}; otherwise it is one for all instances. - When an item runs short,
takeraisesOutOfStockErrorand leaves the warehouse unchanged. - An item taken out in full leaves the dict and
names.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.