Seite 1 von 1
Design-Frage zu args, kwargs
Verfasst: Freitag 24. Juli 2009, 19:17
von danims
Hallo
Ich habe eine Frage, die weniger technischer, sondern eher designtechnischer Natur ist
Ich muss für ein proprietäres Netzwerkprotokoll (entwickelt in unserer Firma) ein Script schreiben, welches halt eben mit dem Service interagieren kann.
Ich steh noch gar nirgends, und hab schon mit Python gearbeitet, aber noch nie wirklich sehr objektorientiert.
Jedes Netzwerkpaketchen hat gewisse Angaben, z.b. Request-Type, TransactionID, Message...
Ich habe mir gedacht, für das Netzwerkpaket eine Klasse zu definieren:
Code: Alles auswählen
class DUMpacket():
"""Class for creating DUM packets"""
def __init__(self):
self.packet = struct('bbb', 1, 1, 3)
Dies ist jetzt nur ein kleiner Ausschnitt. Ich möchte schlussendlich mit dieser Klasse Pakete bauen und diese dann über das socket Modul verschicken. Im Beispiel ist ein Struct mit drei signed chars, aber die sind hardcoded. Wie kann ich am einfachsten Pakete mit diesem Struct, aber mit variablem Inhalt generieren?
Meine Idee war *args und **kwargs, aber ich habe ca. 20 argumente, die in jedem Paket vorkommen MÜSSEN. Nun wird das aber unübersichtlich, 20 argumente in der Funktion stehen zu haben. Sollte man da **kwargs nehmen und in der Funktion überprüfen, ob alle 20 gesetzt sind? Oder gibt es einen besseren Weg? Bin froh um Tipps von euch Profis.
Danke
Verfasst: Freitag 24. Juli 2009, 20:35
von EyDu
Warum nicht so:
Code: Alles auswählen
>>> def spam(arg, missing_arg):
... pass
...
>>> args = dict(arg="42")
>>> spam(**args)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: spam() takes exactly 2 non-keyword arguments (1 given)
>>>
Problematisch ist damit allerdings, dass keine Aussage darüber gemacht wird, welcher Parameter nicht angegeben wurde. Ich vermute mal, dass dies implementierungsabhängig ist. Da ist ein eigener Test natürlich ganz praktisch.
Mich wundert aber die Anzahl der Parameter, dann sind schon sehr viele. Einige lassen sich dabei doch sicher auch zusammenfassen. In deinem Beispiel würde sich zum Beispiel ein Tupel anbieten und keine 3 einzelnen.
Eine mögliche Alternative wäre es, beim Erzeugen nur die nötigen Parameter zu übergeben und die restlichen Attribute zunächst mit Default-Werten belegen. In einem weiteren Schritt füllt man dann den nötigen Rest.
Verfasst: Freitag 24. Juli 2009, 20:41
von BlackJack
@danims: Ich würde auch mal schauen, ob sich von den vielen Argumenten vielleicht welche in Objekten zusammenfassen lassen.
Verfasst: Freitag 24. Juli 2009, 21:10
von Defnull
Etwas komisch, aber so hast du wenigstens sinnvolle Fehlermeldungen.
Code: Alles auswählen
def test(**kargs):
requied = ('arg1','arg2','arg3')
for r in requied:
if r not in kargs:
raise TypeError("You forgot to tell test() about %s. Shame on you!" % r)
Bei Klassen kannst du die Liste mit den notwendigen Parametern ja in ner Klassen- oder Instanzvariable halten.
Verfasst: Freitag 24. Juli 2009, 22:17
von HWK
@Defnull: Du meinst wahrscheinlich 'required', nicht wahr?
MfG
HWK
Verfasst: Samstag 25. Juli 2009, 10:38
von HerrHagen
Oder es werden einem halt gleich alle fehlenden Settings angezeigt:
Code: Alles auswählen
def test(**kwargs):
required = set(('arg1','arg2','arg3'))
missing = required - set(kwargs)
if missing:
raise TypeError("You forgot to tell test() about %s. Shame on you!" % ', '.join(missing))
MFG HerrHagen
Verfasst: Samstag 25. Juli 2009, 11:31
von cofi
Ehrlich gesagt finde ich es etwas seltsam die benoetigten Argumente nicht auch als solche zu kennzeichnen und explizit anzugeben und stattdessen hinter einer manuellen Pruefung zu verstecken.
IMHO ist die Vielzahl der Argumente ein Design Smell und man sollte da wirklich den Weg einschlagen, den BJ vorgeschlagen hat.
Verfasst: Samstag 25. Juli 2009, 11:42
von Dauerbaustelle
Ich würde da einen Preprozessor einbauen, der aus dem Argumenten, die in jeder Funktion benutzt werden, (ein) Objekt(e) macht.
Bsp: Deine benötigten Argumente sind request_url, request_id, transaction_id, message und deine Funktion wird zusätzlich noch mit den Parametern foo und bar aufgerufen:
http://paste.pocoo.org/show/130688/
wobei man `receive_package` auch so schreiben könnte:
http://paste.pocoo.org/show/130689/
Gruß
Verfasst: Samstag 25. Juli 2009, 16:38
von DasIch
Wenn du 20 Argumente hast die benötigt werden dann schreib die alle in die Funktionsdefinition. Der Code mag dann vielleicht nicht unbedingt einen Schönheitswettbewerb gewinnen aber *args, und **kwargs dafür zu Missbrauchen um danach die Argumente zu erzwingen ist lächerlich.
Verfasst: Samstag 25. Juli 2009, 17:19
von Defnull
Wenn man das ganze 'sauber' haben möchte und strickt OO denkt, bräuchte man hier eh ein Transaktions-Objekt, welches erschaffen, in mehreren Schritten mit den nötigen Informationen gefüllt und schließlich (ebenfalls in mehreren Schritten) verarbeitet werden kann.
Danims aller erster Gedanke war also gar nicht mal so falsch, nur das es bei Transaktions-Objekten keinen Sinn macht, alle Daten gleich im Konstruktor zu fordern, da man sie dann ja auch gleich direkt an die send_dumpacket() Methode des Netzwerk-Handlers übergeben könnte.
Stattdessen sollte die DUMpacket Klasse für jede Eigenschaft eine Methode (oder property) definieren, die ihren Input auch gleich noch auf Gültigkeit prüft und richtig formatiert. Bei so komplizierten Paketen macht es eh Sinn, für jede Eigenschaft einen eigenen docstring zu haben. Außerdem kann man noch Methoden wie is_complete() oder get_missing_parameter() definieren, um das Arbeiten und die Fehlersuche einfacher zu gestalten.
Zusätzlich gibts dann noch ne form_packet() Methode, die mit struct() das Paket zusammen bastelt und von send_packet() des Netzwerk-Handlers aufgerufen wird, vorausgesetzt natürlich, is_complete() gibt True zurück.