Ist ** kwargs ein Gegenmuster?

15

Wir haben eine Menge Code in unserer internen Codebasis, der unsere Bibliotheken intern aufruft - diese Bibliotheken haben oft eine Menge Argumente (man denke an matplotlib) und unser Code erledigt oft nur eine bestimmte Aufgabe und übergibt sie einfach **kwargsan die nächste aufgerufene Funktion.

Z.B:

def our_method(dataframe, **kwargs):
    result = do_something_with_data(dataframe)
    external_module.draw(result, **kwargs)

Es **kwargsverhindert zwar, dass wir alle Parameter in unserer Methodendeklaration wiederholen, macht aber auch extrem undurchsichtig, welche Argumente beim Aufruf gültig sind our_method- ich muss wissen, welche Methode aufgerufen wird, was ich oft nicht wissen möchte.

Wie sehen Sie das?

Christian Sauer
quelle

Antworten:

15

Wie wird Ihr Code von Entwicklern verwendet? Mit anderen Worten, was genau tun sie, um zu bestimmen, welche Argumente wie verwendet werden sollen?

  • Wenn sie auf automatisch aus Ihrem Code generierte Dokumentation angewiesen sind und der Generator keine Ahnung hat, was zu tun ist **kwargs, ist dies in der Tat problematisch. Anstatt die Liste der Argumente und ihre Bedeutung in der Dokumentation zu finden, haben sie absolut keine Informationen außer dem vagen „es braucht einige Argumente“.

    Dieses Problem kann möglicherweise behoben werden, indem die Methode manuell dokumentiert und die automatisch generierte Dokumentation ersetzt wird. Dies erfordert zusätzliche Arbeit vom Implementierer der Methode, aber denken Sie daran, dass Code (und seine Dokumentation) viel häufiger gelesen werden als geschrieben.

  • Wenn Code ihre Dokumentation ist, **kwargsbenötigen die Entwickler, die die Methode verwenden, zwei zusätzliche Schritte: Sie müssen nicht nur die Signatur der Methode, sondern auch die tatsächliche Implementierung prüfen, um die andere Methode zu finden, die sie tatsächlich aufruft. Dann müssen sie zu dieser anderen Methode gehen, um endlich zu finden, wonach sie gesucht haben.

    Dies ist nicht mit viel Aufwand verbunden, dennoch sollte der Aufwand immer wieder wiederholt werden. Das Schlimmste ist, dass Sie ihnen nicht helfen können, indem Sie Dokumentation hinzufügen: Wenn Sie Ihre Methode kommentieren und die tatsächlichen Argumente auflisten, besteht ein großes Risiko, dass die nächste Version der Bibliothek, die Ihre Methode aufruft, andere Argumente hat und Ihre Dokumentation veraltet sein, da sich niemand daran erinnern wird, dass es auf dem neuesten Stand gehalten werden muss.

Meine Empfehlung ist, sich **kwargsnur auf Methoden mit eingeschränktem Anwendungsbereich zu verlassen. Private Methoden (und unter privat im Kontext von Python meine ich Methoden, die mit beginnen _), die an wenigen Stellen in der Klasse verwendet werden, sind beispielsweise gute Kandidaten. Andererseits sind Methoden, die von Dutzenden von Klassen in der gesamten Codebasis verwendet werden, sehr schlechte Kandidaten.

Schließlich sollte es nicht zu aufwendig sein, die Argumente einer von Ihnen aufgerufenen Methode innerhalb der von Ihnen geschriebenen Methode neu zu schreiben. Hoffentlich benötigen die meisten Methoden nicht mehr als sechs bis acht Argumente. Wenn ja, fragen Sie sich, ob Sie den Code nicht überarbeiten sollten. Auf alle Fälle:

  • Argumente in Ihrer Methode explizit zu machen, erfordert keinen großen Aufwand.

  • Möglicherweise möchten Sie die Argumente später trotzdem validieren (obwohl Sie YAGNI verletzen, wenn Sie sich nur auf diesen Punkt verlassen, um Argumente explizit zu machen).

Arseni Mourzenko
quelle
Ich mag diese Antwort wirklich und finde sie gut. Leider gibt es in vielen unserer Codes viele öffentliche Methoden, die dieses Muster verwenden. Aber jetzt habe ich Argumente, dass wir es ändern sollten (und Matplotlib fallen lassen, noch nie eine schäbigere "Schnittstelle" gesehen ...)
Christian Sauer
3

Wenn die Funktion der nächsten Ebene ein __doc__ enthält, können Sie das __doc__ einfach in Ihre neue Funktion kopieren.

Beispielsweise:

def a(x):
    """This function takes one parameter, x, and does nothing with it!"""
    pass

def b(**kwargs):
    a(**kwargs)

b.__doc__=a.__doc__

Dies kann rekursiv angewendet werden und kann von einem Dekorateur angewendet werden (was nützlich sein kann, wenn Sie dies trotzdem in großen Mengen tun). Die Zeichenfolge __doc__ kann auch bearbeitet werden, um dem Ende mehr hinzuzufügen. Dies bedeutet, dass die angezeigten Parameter weiterhin kwargs sind, aber es gibt zumindest eine Dokumentation in der Hilfe, die die tatsächlichen Parameter beschreibt.

AMADANON Inc.
quelle