2017-09-10 2 views
1

Beispiel, sagen, dass ich eine Funktionmit `* args`, Schlüsselwortargumente werden positional?

def myfunc(a=1, *args): 
    print(a) 
    # *args have some other functionality, for example passed to a class instance, or whatever 

Problem ist zu definieren: wenn myfunc Aufruf, kann ich die erwarteten Positionsargumente für *args nicht passieren zuerst, dann a Keyword-Argument übergeben. Zum Beispiel, wenn die erwartete *args nur eine Nummer enthalten, kann ich nicht

myfunc(3, a=4) 

Dies würde einen Fehler aus, die besagt, mehrere Zuweisungen für ein empfangenes. Ich kann natürlich nicht myfunc(a=4, b=5, 3) tun. Ich kann

myfunc(4, 3) 

So, jetzt nur tun, wie es scheint, mit dem *args, das Stichwort Argument a wirklich Positions wird?

Das Beispiel hier ist eine Funktion, aber es gilt auch für Klassen. Angenommen, eine Unterklasse mit __init__(a=3, *args) möchte ein Schlüsselwortargument a und einige andere 99 erforderliche Argumente, die an super übergeben werden sollen. Ich möchte nicht die Definition von 99 Argumenten in der Unterklasse wiederholen, sondern *args und übergebe das an super. Aber jetzt wird das Schlüsselwortargument a positionell.

Diese Klasse und Unterklasse Beispiel ist mein echtes Problem in meiner Codierung. Ich möchte eine Unterklasse mit einem optionalen, Schlüsselwortargument a, und dann 99 erforderlichen Argumente für seine Super. Ich sehe keinen guten, sauberen Weg um dieses Problem herum.

Ich kann nicht der Erste sein, der darauf stößt, aber ich konnte keine Antwort finden.

+1

Ich denke, der Weg, es zu tun ist, dein 'a' loszuwerden und stattdessen' * kwargs' zu verwenden. Wenn Sie dann noch einen Standardwert für "a" haben möchten, müssten Sie dies in Ihrer Funktion/Klasse implementieren. Als eine Genauigkeit, erzählt Python '* args' irgendwie" irgendwelche anderen Argumente, die kommen würden ". In Ihrem Beispiel, das einen Fehler verursacht, ordnet Python das erste Argument dem benannten Argument zu, das es kennt ("a"). Wenn Sie also explizit einen Wert für "a" setzen wollen, wird Python verwirrt, da es bereits einen Wert hat dafür. –

+0

Danke für den Kommentar. Du meintest 'Kwargs'? Ja, ich verstehe den Grund, warum das passiert, ich bin nur überrascht, dass mit "* args" die Schlüsselwortargumente positionell werden. Was ich wirklich in meiner eigentlichen Kodierung treffe, ist eher das Beispiel der Klasse und der Unterklasse, und ich sehe keinen guten Weg dafür. – Alex

+0

@Alex. Ich könnte hier etwas vermissen - aber warum nicht einfach die Funktion mit dem Schlüsselwort argument * nach * den '* args' definieren? – ekhumoro

Antwort

2

a ist nicht positions-only:

>>> import inspect 
>>> def myfunc(a=1, *args): 
...  print(a) 
>>> inspect.signature(myfunc).parameters['a'].kind 
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1> 

Und in der Tat kann es in als Keyword-Argument übergeben werden:

>>> myfunc(a=4) 
4 

Doch wie Sie erwähnt, sobald Sie wollen Stellen Sie nachfolgende Positionsargumente zur Verfügung, die Sie als Positionsparameter angeben müssen. Aber das hat nichts mit *args zu tun hat, das ist nur, wie Positionsargumente arbeiten:

>>> def myfunc2(a, b): 
...  print(a) 
>>> myfunc2(1, a=2) 
TypeError: myfunc2() got multiple values for argument 'a' 

Es entweder die erste aller Positionsargumente sein muss oder alle Argumente haben mit Namen übergeben werden.

Jedoch erschwert *args die Situation etwas, weil es nur Positionsargumente sammelt. Das heißt, wenn es irgendetwas sammeln sollte, müssen Sie auch alle vorherigen Parameter nach Position übergeben.

Aber falls Sie Ihren eigenen Sonderfall rollen könnten Sie einfach *args, **kwargs und erste Kontrolle übernehmen, wenn es als Schlüsselwort (benannt) Argument übergeben wird oder, wenn nicht, als erstes Positions Argument:

>>> def myfunc3(*args, **kwargs): 
...  if 'a' in kwargs: 
...   a = kwargs.pop('a') 
...  else: 
...   a = args[0] 
...   args = args[1:] 
...  print(a, args, kwargs) 
>>> myfunc3(1, a=2) 
2 (1,) {} 
>>> myfunc3(2, 1) 
2 (1,) {} 

That sollte Ihnen die Ergebnisse geben, die Sie erwartet haben, aber Sie müssen explizit in der Dokumentation sein, wie das Argument übergeben werden sollte, weil es die Semantik des Python-Signaturmodells etwas durchbricht.

+0

Danke für die Antwort. Die Lösung funktioniert jedoch, wie Sie angemerkt haben, ist es etwas laut für den Code. Ich bin an dieser Stelle ziemlich überrascht. Ich würde mir vorstellen, was ich in der Python-Programmierung eigentlich gerne hätte: Sie haben eine Basisklasse, dann erstellen Sie eine Unterklasse, die einige optionale Schlüsselwortargumente akzeptiert, und umgehen andere Argumente zu Super. Aber anscheinend gibt es keinen sauberen Weg, zumindest in Python 2.7. – Alex

+1

@Alex Ja, es ist ein häufiger Anwendungsfall, aber das Vorhandensein von '* args' und/oder' ** kwargs' zerstört immer die Signatur (zB [inspect.getargspec'] (https://docs.python.org /library/inspect.html#inspect.getargspec) oder ['inspect.signature'] (https://docs.python.org/library/inspect.html#inspect.signature)). In Python ist es üblich, einfach die Parameter zu kopieren und diejenigen hinzuzufügen, die Sie hinzufügen oder entfernen möchten, die Sie nicht benötigen. Denn das bewahrt die Introspektion und erfordert keine "Hacks" wie die, die ich in der Antwort erwähnt habe. – MSeifert