Basis¶
Een kassabon¶
Opgave: Een kassabon
De klasse Product uit de opgave:
class Product:
"""Een product met een naam en een prijs in centen."""
def __init__(self, name, cents):
"""Maak een product met de gegeven naam en prijs in centen."""
self.name = name
self._cents = cents
def price(self):
"""Geeft de prijs in centen."""
return self._cents
def __repr__(self):
"""Geeft het product als regel op de kassabon, zoals brood: 289."""
return f"{self.name}: {self.price()}"
Stap 1: de klasse WeighedProduct¶
De klasse hieronder bevat ook de methode price van stap 2. Zonder die methode
drukt print(kaas) kaas: 1450 af, omdat dan price van Product draait.
class WeighedProduct(Product):
"""Een product dat per kilo wordt verkocht."""
def __init__(self, name, cents_per_kg, grams):
"""Maak een gewogen product; _cents is hier de prijs per kilo."""
super().__init__(name, cents_per_kg)
self._grams = grams
def price(self):
"""Geeft de prijs van het gewicht in centen, naar beneden afgerond."""
return self._cents * self._grams // 1000
kaas = WeighedProduct("kaas", 1450, 400)
assert kaas.name == "kaas"
print(kaas)
kaas: 580
Stap 2: de prijs van een gewogen product¶
1450 * 400 is 580000, en 580000 // 1000 is 580. Bij de druiven is
499 * 250 // 1000 124: 124.75 naar beneden afgerond.
kaas = WeighedProduct("kaas", 1450, 400)
assert kaas.price() == 580
assert repr(kaas) == "kaas: 580"
assert WeighedProduct("appels", 299, 1000).price() == 299
assert WeighedProduct("druiven", 499, 250).price() == 124
assert Product("brood", 289).price() == 289
Stap 3: waarom __repr__ meeverandert¶
Die van
WeighedProduct.selfis in__repr__het object waarvoor de methode wordt aangeroepen, hierkaas, enkaasis eenWeighedProduct. Python zoektpriceeerst in de klasse van het object, en pas daarna in de superklasse. Dat de aanroep in een methode vanProductstaat, maakt daarvoor niet uit.kaas: 1450. Dan leest__repr__de prijs per kilo, en gebruikt ze de berekening vanWeighedProductniet.
Stap 4: de klasse DiscountedProduct¶
super().price() geeft de gewone prijs. De uitwerking bewaart die in full,
zodat de methode price van Product maar één keer wordt aangeroepen.
class DiscountedProduct(Product):
"""Een product in de aanbieding, met een kortingspercentage."""
def __init__(self, name, cents, percent):
"""Maak een product met de gewone prijs cents en percent procent korting."""
super().__init__(name, cents)
self._percent = percent
def price(self):
"""Geeft de prijs na de korting in centen."""
full = super().price()
return full - full * self._percent // 100
koffie = DiscountedProduct("koffie", 599, 25)
assert koffie.price() == 450
assert repr(koffie) == "koffie: 450"
assert DiscountedProduct("thee", 250, 0).price() == 250
assert DiscountedProduct("thee", 250, 100).price() == 0
Stap 5: de klasse Receipt¶
De klasse hieronder bevat ook most_expensive van stap 6 en __repr__ van stap 9.
total gebruikt de verzamelvariabele result.
class Receipt:
"""Een kassabon met de producten die zijn aangeslagen."""
def __init__(self):
"""Maak een lege kassabon."""
self._items = []
def add(self, item):
"""Zet item op de bon."""
self._items.append(item)
def total(self):
"""Geeft het totaal van de bon in centen."""
result = 0
for item in self._items:
result += item.price()
return result
def most_expensive(self):
"""Geeft het duurste item op de bon, of None als de bon leeg is."""
most = None
for item in self._items:
if most is None or item.price() > most.price():
most = item
return most
def __repr__(self):
"""Geeft de bon: elk item op een eigen regel, en daaronder het totaal."""
s = ""
for item in self._items:
s += repr(item) + "\n"
return s + f"totaal: {self.total()}"
bon = Receipt()
assert bon.total() == 0
bon.add(Product("brood", 289))
bon.add(WeighedProduct("kaas", 1450, 400))
bon.add(DiscountedProduct("koffie", 599, 25))
assert bon.total() == 1319
Stap 6: het duurste item¶
most_expensive vergelijkt de items alleen via price(). Bij een lege bon loopt
de lus nul keer, en blijft most dus None.
brood = Product("brood", 289)
kaas = WeighedProduct("kaas", 1450, 400)
bon = Receipt()
assert bon.most_expensive() is None
bon.add(brood)
assert bon.most_expensive() is brood
bon.add(kaas)
bon.add(DiscountedProduct("koffie", 599, 25))
assert bon.most_expensive() is kaas
Stap 7: statiegeld¶
Deposit heeft geen superklasse tussen haakjes.
class Deposit:
"""Statiegeld dat je terugkrijgt; geen product, maar het kan wel op de bon."""
def __init__(self, name, cents):
"""Maak statiegeld van cents centen voor name."""
self.name = name
self._cents = cents
def price(self):
"""Geeft het bedrag in centen, negatief: je krijgt het terug."""
return -self._cents
def __repr__(self):
"""Geeft het statiegeld als regel op de kassabon, zoals fles: -25."""
return f"{self.name}: {self.price()}"
fles = Deposit("fles", 25)
assert fles.price() == -25
assert repr(fles) == "fles: -25"
bon = Receipt()
bon.add(Product("brood", 289))
bon.add(WeighedProduct("kaas", 1450, 400))
bon.add(DiscountedProduct("koffie", 599, 25))
bon.add(fles)
assert bon.total() == 1294
Stap 8: wat mag er op de bon?¶
Alleen
price(). Voor het afdrukken van de bon in stap 9 is daarnaast een__repr__nodig.Dan geeft
bon.total()eenAttributeError:'Deposit' object has no attribute 'price'. Dat merk je pas bijtotalofmost_expensive, wantaddzet het item alleen in de lijst en roept er niets op aan.Het kan: statiegeld heeft ook een naam en een bedrag. Het had dan de constructor en
__repr__vanProductgeërfd, en alleenpricehoeven te overschrijven. Tegelijk is statiegeld iets wat je terugkrijgt, en geen product dat je koopt. Beide keuzes zijn te verdedigen; de bon werkt met allebei, omdat hij alleenprice()aanroept.
Stap 9: de bon afdrukken¶
__repr__ staat in de klasse Receipt bij stap 5. Elk item krijgt een eigen regel
door "\n" erachter, en het totaal komt eronder.
bon = Receipt()
assert repr(bon) == "totaal: 0"
bon.add(Product("brood", 289))
bon.add(WeighedProduct("kaas", 1450, 400))
bon.add(Deposit("fles", 25))
assert repr(bon) == "brood: 289\nkaas: 580\nfles: -25\ntotaal: 844"
print(bon)
brood: 289
kaas: 580
fles: -25
totaal: 844
Stap 10: een eigen soort product¶
Er is niet één goed antwoord. Een voorbeeld: een product per stuk, in een aantal
stuks. Net als bij WeighedProduct is _cents hier de prijs van één eenheid.
class PieceProduct(Product):
"""Een product dat per stuk wordt verkocht, in een aantal stuks."""
def __init__(self, name, cents_per_piece, count):
"""Maak count stuks van name; _cents is hier de prijs van één stuk."""
super().__init__(name, cents_per_piece)
self._count = count
def price(self):
"""Geeft de prijs van alle stuks samen in centen."""
return self._cents * self._count
assert PieceProduct("mandarijnen", 35, 8).price() == 280
assert PieceProduct("mandarijnen", 35, 1).price() == 35
assert PieceProduct("mandarijnen", 35, 0).price() == 0
assert repr(PieceProduct("eieren", 30, 6)) == "eieren: 180"
Stap 11: gewogen én in de aanbieding¶
super().price() is hier de prijs van WeighedProduct: de prijs van het gewicht.
Daar gaat de korting af: 580 - 580 * 25 // 100 is 435.
class DiscountedWeighedProduct(WeighedProduct):
"""Een gewogen product in de aanbieding."""
def __init__(self, name, cents_per_kg, grams, percent):
"""Maak een gewogen product met percent procent korting."""
super().__init__(name, cents_per_kg, grams)
self._percent = percent
def price(self):
"""Geeft de prijs van het gewicht na de korting in centen."""
full = super().price()
return full - full * self._percent // 100
kaas = DiscountedWeighedProduct("kaas", 1450, 400, 25)
assert kaas.price() == 435
assert repr(kaas) == "kaas: 435"
assert DiscountedWeighedProduct("kaas", 1450, 400, 0).price() == 580
In
DiscountedProducten inDiscountedWeighedProduct.Met drie soorten product (gewoon, gewogen, per stuk) en twee soorten korting (percentage, vast bedrag) zijn het drie klassen zonder korting en zes met: negen klassen, en elke nieuwe soort maakt het er meer. De berekening van een percentage korting staat dan in drie klassen, en die van een vast bedrag ook.
Tot slot¶
De vraag in de afsluiting: een korting die een product heeft, heeft alleen
price() nodig om mee te tellen in total en most_expensive, en __repr__ om
op de afgedrukte bon te staan. Meer roept Receipt niet aan.