NullPylung hat geschrieben:Ich muß zugeben, daß mir die fehlende Typisierung anfangs echt Kopfzerbrechen gemacht hat.
Mittlerweile finde ich das Verwenden "sprechender" Variablennamen aber deutlich einfacher.
Meinst du "sprechenden" Variablennamen solche, bei denen der Typname mitkodiert ist? Also so:
Code: Alles auswählen
l_items = ['foo', 'bar', 'baz']
i_counter = 0
i_counter += 1
usw? Dann wiederhole ich nochmal: vermeide das. Wer sich aus dem Namen
counter nicht zusammenreimen kann, dass hier etwas gezählt wird, dem wird ein vorangestelltes
i_ auch nicht weiterhelfen. Und zählen tun wir Menschen nunmal primär mit ganzen Zahlen, nicht mit Strings, IP-Sockets oder Regex-Matches (oder was es halt sonst so an Objekten gibt).
Bei dem Beispiel mit
l_items ist es so, dass eine Funktion, die über die darüber iterieren und mit den Elementen irgendetwas tun soll, überhaupt nicht wissen muss, dass sie eine Liste vor sich hat. Elementweises iterieren geht über viele Datenstrukturen, nicht nur über Listen. Und für den Fall, dass sie Elemente hinzufügen oder löschen soll: eine Funktion sollte als Argumente übergebene Datenstrukturen idR. nicht verändern. Das führt langfristig nur zu Schmerz und Tränen. Statt dessen sollte man die Datenstruktur so wegkapseln, dass es zentrale Stellen im Code gibt, wo sie manipuliert wird. Beispiel:
Code: Alles auswählen
from uuid import uuid4
class LineItem:
def __init__(self, article_id, amount):
self.article_id = article_id
self.amount = amount
class Cart:
def __init__(self, customer_id):
self.id = uuid4()
self._items = []
def __iter__(self):
return iter(self._items)
def add_line_item(self, article_id, amount):
if amount < 1:
raise ValueError('Amount must be greater than zero!')
self._items.append(LineItem(article_id, amount))
Der Code ist unmittelbar einleuchtend, aber auch ungünstig:
Code: Alles auswählen
c = Cart()
c.add_line_item(123, 5)
c.add_line_item(123, 7)
Wollen wir wirklich in ein und demselben Warenkorb mehrmals dieselbe Sache mit jeweils unterschiedlichen Mengen stehen haben? Oder nur einmal mit der Gesamtmenge? Und wie sähe eine
remove_line_item() Methode aus? Soll sie alle items mit derselben Artikel-ID löschen? Oder soll man angeben, wieviele gelöscht werden sollen? Habe ich im Beispiel oben nun -1 und 7 oder 5 und 1 oder gleich 6 stehen? Nehmen wir also an, pro Artikel-ID darf es nur ein LineItem im Warenkorb geben, und
add_line_item() und
remove_line_item() müssen diese Invariante aufrecht erhalten. Dann käme zB. sowas heraus:
Code: Alles auswählen
def add_line_item(self, article_id, amount):
if amount < 1:
raise ValueError('Amount must be greater than zero!')
try:
index = self._items[article_id]
except ValueError;
self._items.append(LineItem(article_id, amount))
else:
self._items[index].amount += amount
def remove_line_item(self, article_id, amount):
index = self._items.index(article_id)
item = if self._items[index]
if item.amount > amount:
self._items[index].amount -= amount
else:
del self._items[index]
Ziemlich umständlich, finde ich. Dann lieber gleich mit einer Assoziativen Datenstruktur:
Code: Alles auswählen
class LineItems(dict):
def __missing__(self, article_id):
item = LineItem(article_id, 0)
self[article_id] = item
return item
class Cart:
def __init__(self, customer_id):
self.id = uuid4()
self._items = LineItems()
def __iter__(self):
return self._items.values()
def add_line_item(self, article_id, amount):
if amount < 1:
raise ValueError('Amount must be greater than zero!')
self._items[article_id].amount += amount
def remove_line_item(self, article_id, amount):
item = self._items[article_id]
if item.amount > amount:
item.amount -= amount
else:
del self._items[article_id]
Anmerkung 1: der
einzelne führende Unterstriche bei den Attributnamen bedeutet: Implementierungsdetail, bitte nicht von außen verwenden. Das ist reine Konvention, und wer mag, kann trotzdem darauf zugreifen. Wer sich selber in den Fuß schießen will, indem er auf Implementierungsdetails zugreift, kann das gerne tun. Ist ja sein Fuß.
Anmerkung 2:
Doku zur __missing__() Methode.
Was haben wir erreicht? Wir haben die Implementation ändern können, ohne dass sich irgendwas daran, wie ein Warenkorb verwendet wird, geändert hat. Und wir haben keine Ungarische Notation gebraucht. Der Bereich, in dem
_items sichtbar ist, ist so klein, dass man einfach nachsehen kann, auf was dieser Name verweist. Weil wir ihn als Implementierungsdetail gekennzeichnet haben, können wir davon ausgehen, dass er auf nichts anderes als ein
LineItems-Objekt verweist, denn in unserem Code wird
self._items nur einmal am Anfang gesetzt und nie überschrieben. Und man kann über ein
Cart-Objekt iterieren und erhält dabei jedes
LineItem, denn wir haben das Iterator-Protokoll implementiert. Wie wir das getan haben, geht den Benutzer eines
Cart-Objektes nichts an.