wake-up-neo.net

Erzwungene Benennung von Parametern in Python

In Python haben Sie möglicherweise eine Funktionsdefinition:

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

die auf eine der folgenden Arten aufgerufen werden kann:

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

dank Pythons Argumenten beliebiger Ordnung, solange sie benannt werden. 

Das Problem, das wir haben, ist, während einige unserer größeren Funktionen zunehmen. Die Leute fügen möglicherweise Parameter zwischen spacing und collapse hinzu, was bedeutet, dass die falschen Werte möglicherweise zu Parametern führen, die nicht benannt sind. Darüber hinaus ist es manchmal nicht immer klar, was dabei zu beachten ist. Wir suchen nach einem Weg, um Leute dazu zu bringen, bestimmte Parameter zu benennen - nicht nur ein Kodierungsstandard, sondern im Idealfall ein Flag- oder Pydev-Plugin.

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

Wahrscheinlich werden wir es nur für bestimmte Funktionen aktivieren, aber alle Vorschläge, wie Sie dies implementieren können - oder ob es überhaupt möglich ist, würden uns freuen.

64
Mark Mayo

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

Von docs :

Parameter nach “*” oder “* bezeichner” sind nur für Schlüsselwörter und Es dürfen nur 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 mit **kwargs kombiniert werden:

def foo(pos, *, forcenamed, **kwargs):
138
Eli Bendersky

Sie können die Verwendung von Schlüsselwortargumenten in Python3 erzwingen, indem Sie eine Funktion auf folgende Weise 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, die Schlüsselwortargumente zu verwenden. Dies ist, was Sie meiner Meinung nach gefragt haben. In Python2 besteht die einzige Möglichkeit darin, eine solche Funktion zu definieren

def foo(**kwargs):
    pass

Das zwingt den Aufrufer dazu, kwargs zu verwenden, aber dies ist keine großartige Lösung, da Sie dann einen Haken setzen müssen, um nur das Argument zu akzeptieren, das Sie benötigen.

23
martega

Richtig, die meisten Programmiersprachen machen die Parameterreihenfolge zum Bestandteil des Funktionsaufrufvertrags, aber muss nicht sein. Warum sollte es Ich verstehe die Frage also, wenn sich Python in dieser Hinsicht von anderen Programmiersprachen unterscheidet. Bitte beachten Sie zusätzlich zu 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)

Die einzige Möglichkeit, mit der ein Aufrufer die Argumente spacing und collapse positionell (ohne Ausnahme) bereitstellen könnte, 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 wird diese Konvention für Parameter nur teilweise durchgesetzt.

Andernfalls müssten Aufrufe die folgende Form haben:

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

Ein Anruf

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

würde dem Parameter _p den Wert 11 zuweisen und eine Ausnahme wird durch die erste Anweisung der Funktion ausgelöst.

Eigenschaften:

  • Parameter vor _p=__named_only_start werden positionell (oder nach Name) zugelassen.
  • Die Parameter hinter _p=__named_only_start müssen nur über den Namen angegeben werden (es sei denn, das Wissen über das spezielle Sentinel-Objekt __named_only_start wird abgerufen und verwendet).

Pros:

  • Parameter sind in Anzahl und Bedeutung explizit (die späteren guten Namen werden natürlich auch gewählt).
  • Wenn das Sentinel als erster Parameter angegeben ist, müssen alle Argumente anhand des Namens angegeben werden.
  • Beim Aufruf der Funktion ist es möglich, in den Positionsmodus zu wechseln, indem Sie das Sentinel-Objekt __named_only_start an der entsprechenden Position verwenden.
  • Eine bessere Leistung als andere Alternativen kann erwartet werden.

Nachteile:

  • Die Überprüfung erfolgt zur Laufzeit und nicht zur Kompilierzeit.
  • Verwendung eines zusätzlichen Parameters (jedoch nicht eines Arguments) und einer zusätzlichen Prüfung. Geringe Leistungseinbußen in Bezug auf reguläre Funktionen.
  • Funktionalität ist ein Hack ohne direkte Unterstützung der Sprache (siehe Hinweis unten).
  • Wenn Sie die Funktion aufrufen, können Sie in den Positionsmodus wechseln, indem Sie das Sentinel-Objekt __named_only_start an der richtigen Position verwenden. Ja, das kann auch als Profi gesehen werden.

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

Ich habe herausgefunden, dass, wenn ich meine Gedanken öffne und darüber nachdenke, keine Entscheidung der einen oder anderen Frage dumm, dumm oder einfach dumm erscheint. Im Gegenteil: Ich lerne normalerweise sehr viel.

9
Mario Rossi

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

_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 wird erlauben:

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 haben und info(odbchelper) funktioniert nicht mehr.

Auf diese Weise können Sie zusätzliche Schlüsselwortargumente an beliebiger Stelle nach _kw platzieren, ohne dass Sie sie zwingen müssen, sie nach der letzten Eingabe einzufügen. Dies ist oft sinnvoll, z. Das logische Gruppieren von Dingen oder das alphabetische Anordnen von Stichwörtern kann bei der Pflege und Entwicklung helfen.

Sie müssen also nicht auf def(**kwargs) zurückgreifen und die Signaturinformationen in Ihrem intelligenten Editor verlieren. Ihr Gesellschaftsvertrag besteht darin, bestimmte Informationen bereitzustellen, indem Sie (einige von ihnen) dazu zwingen, Schlüsselwörter einzugeben. Die Reihenfolge, in der diese angezeigt werden, ist nicht mehr relevant.

7
Anthon

pdate:

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

def info(foo, **kwargs):

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

Es kommt wirklich darauf an, was Bryan sagt.


(...) Möglicherweise werden Parameter zwischen spacing und collapse hinzugefügt. (...)

Im Allgemeinen sollten neue Argumente beim Ändern von Funktionen immer am Ende stehen. Ansonsten bricht es den Code. Sollte offensichtlich sein.
Wenn jemand die Funktion so ändert, dass der Code kaputt geht, muss diese Änderung verworfen werden.
(Wie Bryan sagt, ist es wie ein Vertrag)

(...) Manchmal ist nicht immer klar, was man tun muss.

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


Alte Antwort (der Vollständigkeit halber) :

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')

kwargs ist ein Wörterbuch, das ein 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 einem richtigen Dokumentationsstring sollte es in Ordnung sein.

2
Felix Kling

Sie können erklären, dass Ihre Funktionen nur **args empfangen. Dies 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
Noufal Ibrahim

Die Nur-Schlüsselwort-Argumente von python3 (*) können in python2.x mit **kwargs simuliert werden.

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. Beachten Sie, dass ich mir die Freiheit genommen habe, TypeError in KeyError zu ändern, wenn ein "erforderliches Argument" angegeben wird. Es wäre nicht zu aufwendig, diesen Ausnahmetyp ebenfalls zu verwenden

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 aber vermieden werden, wenn Sie nur python3.x sind

1
Anthony Sottile

Sie können den Operator ** verwenden:

def info(**kwargs):

auf diese Weise müssen die Benutzer benannte Parameter verwenden.

0
Olivier Verdier

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

Wenn Sie dies dennoch tun möchten, verwenden Sie die Funktion function decorator und die Funktion inspect.getargspec . Es würde so etwas verwendet werden:

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

Die Implementierung von require_named_args bleibt dem Leser als Übung überlassen.

Ich würde es nicht stören. Es wird jedes Mal langsam, wenn die Funktion aufgerufen wird, und Sie erhalten bessere Ergebnisse, wenn Sie Code sorgfältiger schreiben.

0
Daniel Newby
def cheeseshop(kind, *arguments, **keywords):

in Python, wenn use * args bedeutet, können Sie für diesen Parameter eine n-Anzahl von Argumenten übergeben - auf die eine Liste innerhalb der Funktion zugreifen soll

und wenn use ** kw das bedeutet, seine Schlüsselwortargumente, das kann als diktiert werden - Sie können n-number von kw args übergeben, und wenn Sie den Benutzer einschränken möchten, müssen Sie die Reihenfolge und die Argumente der Reihe nach eingeben * und ** - (Pythonische Art, generische Lösungen für große Architekturen bereitzustellen ...)

wenn Sie Ihre Funktion mit Standardwerten einschränken möchten, können Sie darin nachsehen

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