Design-Frage zu args, kwargs

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
danims
User
Beiträge: 29
Registriert: Montag 19. Februar 2007, 20:23
Wohnort: Bern, Schweiz

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
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
Das Leben ist wie ein Tennisball.
BlackJack

@danims: Ich würde auch mal schauen, ob sich von den vielen Argumenten vielleicht welche in Objekten zusammenfassen lassen.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

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.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

@Defnull: Du meinst wahrscheinlich 'required', nicht wahr?
MfG
HWK
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

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
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

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.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

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ß
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

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.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

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.
Bottle: Micro Web Framework + Development Blog
Antworten