Problem overview
A shop tracks how many pieces of each item it has. The stock can never go below 0, and it changes only when pieces are sold or new ones arrive. So the class InventoryItem keeps the stock private and changes it only through two methods that check the rules. The name has no rules to protect, so it stays a plain attribute that any code can read.
InventoryItem(name, stock)creates an item with that name and that many pieces.sell(n)lowers the stock bynand returnsTruewhennis at least 1 and at most the stock. Otherwise it changes nothing and returnsFalse.restock(n)addsnto the stock whennis at least 1, and ignores it otherwise. It returns nothing.get_stock()returns the stock.
With 5 pencils, sell(3) returns True and leaves 2. Then sell(5) returns False, because only 2 are left, and the stock stays 2. After restock(10) there are 12. The lesson's tests print the booleans as True and False.
Approach: check first, change second
sell must refuse three kinds of request: 0 pieces, a negative number of pieces and more pieces than are in stock. All three are caught by one test: n < 1 or n > self._stock. The check comes before any change, so a refused sale leaves the stock exactly as it was. Only a sale that passes gets to the line that lowers the stock, so the stock can't go below 0.
Selling exactly the whole stock is allowed. That is why the upper test is n > self._stock and not n >= self._stock.
restock is the mirror image. A delivery of 0 or a negative number isn't a delivery, so the method ignores it. A single if n >= 1 around the addition does it.
Step by step
- Store the name in
self.name. It has no rule to protect, so other code may read it directly. - Store the stock in
self._stock. The underscore is Python's convention for "private": other code should go through the methods. - In
sell, write the refusal first: aniffornbelow 1 or aboveself._stock, whose body returnsFalse. - Let the sale through: lower
self._stockbynand returnTrue. - In
restock, addntoself._stockonly whenn >= 1. get_stockreturnsself._stock. Other code can see the stock through it but never set it.
The solution in Python
class InventoryItem:
def __init__(self, name, stock):
self.name = name
self._stock = stock
def sell(self, n):
if n < 1 or n > self._stock:
return False
self._stock -= n
return True
def restock(self, n):
if n >= 1:
self._stock += n
def get_stock(self):
return self._stock
Using it:
item = InventoryItem("Lamp", 2)
print(item.sell(0), item.sell(-3))
item.restock(0)
item.restock(-5)
print(item.get_stock())
item.restock(1)
print(item.sell(3), item.get_stock())
False False
2
True 0
Both refused sales and both ignored deliveries leave the stock at 2. After restock(1) there are 3 lamps, and selling all 3 works.
Python details
- A method without
returngivesNone.restockreturns nothing, soprint(item.restock(3))printsNone. orstops early. Inn < 1 or n > self._stock, if the first part is true the second isn't evaluated. Either way the result is a refusal.- Private by convention. Python doesn't block
item._stock = -50from outside. The underscore asks other code not to do it, and the class stays correct as long as everyone follows that.
Edge cases and common mistakes
- Selling the whole stock. With 4 mugs,
sell(4)returnsTrueand leaves 0. After that evensell(1)returnsFalse. - Checking after the change. If
selllowered the stock first and tested afterwards, a refused sale would already have changed it. - Using
andinstead ofor. No number is both below 1 and above the stock, so the refusal would never fire. - Returning a value from
restock. The lesson says it returns nothing. - Complexity. Each method is a comparison and one addition or subtraction, so each runs in O(1) time.
