Erzwungene Benennung von Parametern in Python


111

In Python haben Sie möglicherweise eine Funktionsdefinition:

def info(object, spacing=10, collapse=1)

Dies kann auf eine der folgenden Arten aufgerufen werden:

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

dank Pythons Erlaubnis von Argumenten beliebiger Reihenfolge, solange sie benannt sind.

Das Problem, das wir haben, ist, dass einige unserer größeren Funktionen wachsen und möglicherweise Parameter zwischen spacingund hinzugefügt werden collapse, was bedeutet, dass die falschen Werte möglicherweise zu Parametern gehen, die nicht benannt sind. Außerdem ist manchmal nicht immer klar, was zu tun ist. Wir sind auf der Suche nach einer Möglichkeit, Leute dazu zu zwingen, bestimmte Parameter zu benennen - nicht nur einen Codierungsstandard, sondern im Idealfall ein Flag- oder Pydev-Plugin?

so dass in den obigen 4 Beispielen nur der letzte die Prüfung bestehen würde, da alle Parameter benannt sind.

Die Chancen stehen gut, dass wir es nur für bestimmte Funktionen aktivieren, aber alle Vorschläge, wie dies implementiert werden kann - oder ob es überhaupt möglich ist, wären willkommen.

Antworten:


213

In Python 3 - Ja können Sie *in der Argumentliste angeben .

Aus Dokumenten :

Parameter nach "*" oder "* identifier" sind reine Schlüsselwortparameter und dürfen nur über verwendete Schlüsselwortargumente übergeben werden.

>>> def foo(pos, *, forcenamed):
...   print(pos, forcenamed)
... 
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)

Dies kann auch kombiniert werden mit **kwargs:

def foo(pos, *, forcenamed, **kwargs):

31

Sie können Benutzer zwingen, Schlüsselwortargumente in Python3 zu verwenden, indem Sie eine Funktion folgendermaßen definieren.

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass

Indem Sie das erste Argument zu einem Positionsargument ohne Namen machen, zwingen Sie jeden, der die Funktion aufruft, dazu, die Schlüsselwortargumente zu verwenden, nach denen Sie meiner Meinung nach gefragt haben. In Python2 können Sie dies nur tun, indem Sie eine solche Funktion definieren

def foo(**kwargs):
    pass

Das zwingt den Aufrufer, kwargs zu verwenden, aber dies ist keine so gute Lösung, da Sie dann eine Prüfung durchführen müssten, um nur das Argument zu akzeptieren, das Sie benötigen.


11

Es stimmt, die meisten Programmiersprachen machen Parameter , um einen Teil des Funktionsaufrufs Vertrag, aber dies nicht brauchen , so zu sein. Warum sollte es? Mein Verständnis der Frage ist also, ob Python sich in dieser Hinsicht von anderen Programmiersprachen unterscheidet. Beachten Sie neben anderen guten Antworten für Python 2 Folgendes:

__named_only_start = object()

def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
    if _p is not __named_only_start:
        raise TypeError("info() takes at most 3 positional arguments")
    return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)

Der einzige Weg, wie ein Anrufer Argumente spacingund collapsePosition (ohne Ausnahme) liefern kann, wäre:

info(arg1, arg2, arg3, module.__named_only_start, 11, 2)

Die Konvention, keine privaten Elemente zu verwenden, die bereits zu anderen Modulen gehören, ist in Python sehr grundlegend. Wie bei Python selbst würde diese Konvention für Parameter nur teilweise durchgesetzt.

Andernfalls müssten Anrufe die folgende Form haben:

info(arg1, arg2, arg3, spacing=11, collapse=2)

Ein Anruf

info(arg1, arg2, arg3, 11, 2)

würde dem Parameter den Wert 11 zuweisen _pund eine Ausnahme durch den ersten Befehl der Funktion auslösen.

Eigenschaften:

  • Vorherige Parameter _p=__named_only_startwerden positionell (oder namentlich) zugelassen.
  • Parameter nach _p=__named_only_startmüssen nur namentlich angegeben werden (es sei denn, Kenntnisse über das spezielle Sentinel-Objekt __named_only_startwerden erhalten und verwendet).

Vorteile:

  • Parameter sind in Anzahl und Bedeutung explizit (später, wenn natürlich auch gute Namen gewählt werden).
  • Wenn der Sentinel als erster Parameter angegeben wird, müssen alle Argumente namentlich angegeben werden.
  • Beim Aufrufen der Funktion ist es möglich, in den Positionsmodus zu wechseln, indem das Sentinel-Objekt __named_only_startan der entsprechenden Position verwendet wird.
  • Eine bessere Leistung als bei anderen Alternativen ist zu erwarten.

Nachteile:

  • Die Überprüfung erfolgt zur Laufzeit, nicht zur Kompilierungszeit.
  • Verwendung eines zusätzlichen Parameters (wenn auch kein Argument) und einer zusätzlichen Prüfung. Geringe Leistungsverschlechterung in Bezug auf reguläre Funktionen.
  • Funktionalität ist ein Hack ohne direkte Unterstützung durch die Sprache (siehe Hinweis unten).
  • Beim Aufrufen der Funktion kann mit dem Sentinel-Objekt __named_only_startin der richtigen Position in den Positionsmodus gewechselt werden . Ja, das kann man auch als Profi sehen.

Bitte beachten Sie, dass diese Antwort nur für Python 2 gültig ist. Python 3 implementiert den ähnlichen, aber sehr eleganten, sprachunterstützten Mechanismus, der in anderen Antworten beschrieben wird.

Ich habe festgestellt, dass, wenn ich meinen Geist öffne und darüber nachdenke, keine Frage oder die Entscheidung eines anderen dumm, dumm oder einfach nur albern erscheint. Ganz im Gegenteil: Ich lerne normalerweise viel.


"Die Überprüfung erfolgt zur Laufzeit, nicht zur Kompilierungszeit." - Ich denke, das gilt für alle Funktionsargumentprüfungen. Bis Sie die Zeile des Funktionsaufrufs tatsächlich ausführen, wissen Sie nicht immer, welche Funktion ausgeführt wird. Auch +1 - das ist klug.
Eric

@ Eric: Es ist nur so, dass ich die statische Überprüfung vorgezogen hätte. Aber Sie haben Recht: Das wäre überhaupt nicht Python gewesen. Obwohl dies kein entscheidender Punkt ist, wird das "*" - Konstrukt von Python 3 auch dynamisch überprüft. Vielen Dank für Ihren Kommentar.
Mario Rossi

Wenn Sie die Modulvariable benennen _named_only_start, ist es auch unmöglich, sie von einem externen Modul aus zu referenzieren, das ein Pro und ein Contra herausnimmt. (einzelne führende Unterstriche im Modulumfang sind privat, IIRC)
Eric

In Bezug auf die Benennung des Sentinels könnten wir auch sowohl ein __named_only_startals auch ein named_only_start(kein anfänglicher Unterstrich) haben, wobei der zweite angibt, dass der benannte Modus "empfohlen" wird, jedoch nicht auf der Ebene der "aktiven Werbung" (da einer öffentlich ist und der andere ist nicht). Die "Privatsphäre" _names, mit Unterstrichen zu beginnen, wird von der Sprache nicht sehr stark durchgesetzt: Sie kann leicht durch die Verwendung spezifischer (nicht *) Importe oder qualifizierter Namen umgangen werden. Aus diesem Grund bevorzugen einige Python-Dokumente den Begriff "nicht öffentlich" anstelle von "privat".
Mario Rossi

6

Sie können dies auf eine Weise tun, die sowohl in Python 2 als auch in Python 3 funktioniert , indem Sie ein "falsches" erstes Schlüsselwortargument mit einem Standardwert erstellen, der nicht "natürlich" vorkommt. Vor diesem Schlüsselwortargument können ein oder mehrere Argumente ohne Wert stehen:

_dummy = object()

def info(object, _kw=_dummy, spacing=10, collapse=1):
    if _kw is not _dummy:
        raise TypeError("info() takes 1 positional argument but at least 2 were given")

Dies ermöglicht:

info(odbchelper)        
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

aber nicht:

info(odbchelper, 12)                

Wenn Sie die Funktion ändern in:

def info(_kw=_dummy, spacing=10, collapse=1):

dann müssen alle Argumente Schlüsselwörter und haben info(odbchelper) funktionieren nicht mehr.

Auf diese Weise können Sie zusätzliche Keyword-Argumente an einer beliebigen Stelle danach platzieren _kw , ohne sie nach dem letzten Eintrag einfügen zu müssen. Dies ist oft sinnvoll, z. B. kann die logische Gruppierung oder alphabetische Anordnung von Schlüsselwörtern bei der Wartung und Entwicklung hilfreich sein.

Sie müssen also nicht mehr auf die Verwendung def(**kwargs)und den Verlust der Signaturinformationen in Ihrem Smart Editor zurückgreifen . Ihr Gesellschaftsvertrag besteht darin, bestimmte Informationen bereitzustellen, indem Sie (einige von ihnen) dazu zwingen, Schlüsselwörter zu verlangen. Die Reihenfolge, in der diese dargestellt werden, ist irrelevant geworden.


2

Aktualisieren:

Ich erkannte, dass die Verwendung **kwargsdas Problem nicht lösen würde. Wenn Ihre Programmierer Funktionsargumente nach Belieben ändern, können Sie beispielsweise die Funktion folgendermaßen ändern:

def info(foo, **kwargs):

und der alte Code würde wieder brechen (weil jetzt jeder Funktionsaufruf das erste Argument enthalten muss).

Es kommt wirklich darauf an, was Bryan sagt.


(...) Personen fügen möglicherweise Parameter zwischen spacingund collapse(...) hinzu.

Im Allgemeinen sollten beim Ändern von Funktionen neue Argumente immer bis zum Ende gehen. Andernfalls wird der Code beschädigt. Sollte offensichtlich sein.
Wenn jemand die Funktion so ändert, dass der Code beschädigt wird, muss diese Änderung abgelehnt werden.
(Wie Bryan sagt, ist es wie ein Vertrag)

(...) manchmal ist nicht immer klar, was reingehen muss.

Wenn man sich die Signatur der Funktion (dh def info(object, spacing=10, collapse=1)) ansieht, sollte man sofort erkennen, dass jedes Argument, das keinen Standardwert hat, obligatorisch ist.
Wofür das Argument ist, sollte in den Docstring gehen.


Alte Antwort (der Vollständigkeit halber aufbewahrt) :

Dies ist wahrscheinlich keine gute Lösung:

Sie können Funktionen folgendermaßen definieren:

def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')

kwargsist ein Wörterbuch, das ein beliebiges Schlüsselwortargument enthält. Sie können überprüfen, ob ein obligatorisches Argument vorhanden ist, und wenn nicht, eine Ausnahme auslösen.

Der Nachteil ist, dass es vielleicht nicht mehr so ​​offensichtlich ist, welche Argumente möglich sind, aber mit einer richtigen Dokumentation sollte es in Ordnung sein.


3
Ihre alte Antwort hat mir besser gefallen. Geben Sie einfach einen Kommentar ein, warum Sie nur ** kwargs in der Funktion akzeptieren. Schließlich kann jeder den Quellcode ändern - Sie benötigen eine Dokumentation, um die Absichten und den Zweck Ihrer Entscheidungen zu beschreiben.
Brandon

Diese Antwort enthält keine tatsächliche Antwort!
Phil

2

Die python3-Schlüsselwortargumente ( *) können in python2.x mit simuliert werden**kwargs

Betrachten Sie den folgenden Python3-Code:

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

und sein Verhalten:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

Dies kann wie folgt simuliert werden: Hinweis: Ich habe mir die Freiheit genommen TypeError, KeyErrorim Fall "Erforderliches benanntes Argument" zu wechseln. Es wäre nicht zu viel Arbeit, diesen Ausnahmetyp ebenfalls festzulegen

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

Und Verhalten:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

Das Rezept funktioniert genauso gut in python3.x, sollte jedoch vermieden werden, wenn Sie nur python3.x sind


Ah, also kwargs.pop('foo')ist eine Python 2-Sprache? Ich muss meinen Codierungsstil aktualisieren. Ich habe diesen Ansatz immer noch in Python 3 verwendet
Neil

0

Sie können Ihre Funktionen als **argsnur empfangend deklarieren . Das würde Schlüsselwortargumente erfordern, aber Sie müssten zusätzliche Arbeit leisten, um sicherzustellen, dass nur gültige Namen übergeben werden.

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.

1
Sie müssen nicht nur Schlüsselwortprüfungen hinzufügen, sondern auch an einen Verbraucher denken, der weiß, dass er eine Methode mit der Signatur aufrufen muss foo(**kwargs). Was gebe ich dazu? foo(killme=True, when="rightnowplease")
Dagrooms

Es kommt wirklich darauf an. Überlegen Sie dict.
Noufal Ibrahim

0

Wie andere Antworten sagen, ist das Ändern von Funktionssignaturen eine schlechte Idee. Fügen Sie am Ende entweder neue Parameter hinzu oder korrigieren Sie jeden Aufrufer, wenn Argumente eingefügt werden.

Wenn Sie dies dennoch tun möchten, verwenden Sie einen Funktionsdekorator und die Funktion inspect.getargspec . Es würde ungefähr so ​​verwendet werden:

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

Implementierung von require_named_args bleibt als Übung für den Leser.

Ich würde mich nicht darum kümmern. Es wird jedes Mal langsam, wenn die Funktion aufgerufen wird, und Sie erhalten bessere Ergebnisse, wenn Sie den Code sorgfältiger schreiben.


-1

Sie könnten den **Operator verwenden:

def info(**kwargs):

Auf diese Weise werden Benutzer gezwungen, benannte Parameter zu verwenden.


2
Und haben keine Ahnung, wie Sie Ihre Methode aufrufen können, ohne Ihren Code zu lesen, was die kognitive Belastung Ihres Verbrauchers
erhöht

Aus dem genannten Grund ist dies eine wirklich schlechte Praxis und sollte vermieden werden.
David S.

-1
def cheeseshop(kind, *arguments, **keywords):

Wenn Sie in Python * args verwenden, bedeutet dies, dass Sie n-Anzahl von Argumenten für diesen Parameter übergeben können. Dies ist eine Liste innerhalb der Funktion, auf die zugegriffen werden soll

und wenn Sie ** kw verwenden, bedeutet dies, dass die Schlüsselwortargumente als Diktat verwendet werden können - Sie können n-Anzahl von kw-Argumenten übergeben, und wenn Sie diesen Benutzer einschränken möchten, müssen Sie die Reihenfolge und die Argumente der Reihe nach eingeben, dann verwenden Sie sie nicht * und ** - (seine pythonische Art, generische Lösungen für große Architekturen bereitzustellen ...)

Wenn Sie Ihre Funktion mit Standardwerten einschränken möchten, können Sie sie überprüfen

def info(object, spacing, collapse)
  spacing = spacing or 10
  collapse = collapse or 1

Was passiert, wenn der Abstand 0 sein soll? (Antwort, du bekommst 10). Diese Antwort ist aus den gleichen Gründen genauso falsch wie alle anderen ** kwargs-Antworten.
Phil

-2

Ich verstehe nicht, warum ein Programmierer überhaupt einen Parameter zwischen zwei anderen hinzufügt.

Wenn Sie möchten, dass die Funktionsparameter mit Namen verwendet werden (z info(spacing=15, object=odbchelper) ), sollte es keine Rolle spielen, in welcher Reihenfolge sie definiert sind. Sie können also auch die neuen Parameter am Ende setzen.

Wenn Sie möchten, dass die Reihenfolge eine Rolle spielt, können Sie nicht erwarten, dass etwas funktioniert, wenn Sie es ändern!


2
Dies beantwortet die Frage nicht. Ob es eine gute Idee ist oder nicht, spielt keine Rolle - jemand könnte es trotzdem tun.
Graeme Perrow

1
Wie Graeme erwähnte, wird es sowieso jemand tun. Wenn Sie eine Bibliothek schreiben, die von anderen verwendet werden soll, bietet das Erzwingen (nur Python 3) der Übergabe von Argumenten nur für Schlüsselwörter zusätzliche Flexibilität, wenn Sie Ihre API umgestalten müssen.
s0undt3ch
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.