Alle Argumente und Werte an eine Funktion übergeben

75

Ich habe eine Python-Funktion, fetch_datadie eine Remote-API aufruft, einige Daten abruft und sie in ein Antwortobjekt zurückgibt. Es sieht ein bisschen wie folgt aus:

def fetch_data(self, foo, bar, baz, **kwargs):
    response = Response()
    # Do various things, get some data
    return response

Jetzt ist es möglich, dass in den Antwortdaten "Ich habe mehr Daten, rufe mich mit einem inkrementierten pageParameter an, um mehr zu erhalten" steht. Daher möchte ich im Wesentlichen "The Method Call" (Funktion, Parameter) im Antwortobjekt speichern, damit ich dann eine haben kann, Response.get_more()die die gespeicherte Funktion und die gespeicherten Parameter betrachtet und die Funktion mit (fast) derselben erneut aufruft Parameter, eine neue zurückgebenResponse

Wenn nun fetch_datawurden definiert als fetch_data(*args, **kwargs)ich gerade speichern könnte (fetch_data, args, kwargs)in response. Allerdings habe ich self, foo, barund bazzur Sorge - ich konnte einfach speichern , (fetch_data, foo, bar, baz, kwargs)aber das ist eine höchst unerwünschte Menge an Wiederholungen.

Im Wesentlichen versuche ich herauszufinden, wie aus einer Funktion heraus ein vollständig ausgefülltes *argsund **kwargseinschließlich der benannten Parameter der Funktion erstellt werden kann.

Kristian Glass
quelle
Warum nicht foo, bar, baz und kwargs an den Response () -Konstruktor übergeben, damit der spätere Aufruf von response.get_more () diese Werte bereits hat?
Kurosch
Weil das das Problem nicht behebt - ich müsste noch berühren, Responsewenn ich die Signatur von ändern würde fetch_data.
Kristian Glass

Antworten:

99

Im Wesentlichen versuche ich herauszufinden, wie innerhalb einer Funktion vollständig ausgefüllte * args und ** kwargs abgerufen werden können, einschließlich der benannten Parameter der Funktion.

Wie wäre es, wenn Sie die Argumente über locals()zu Beginn der Funktion speichern ?

def my_func(a, *args, **kwargs):
    saved_args = locals()
    print("saved_args is", saved_args)
    local_var = 10
    print("saved_args is", saved_args)
    print("But locals() is now", locals())

my_func(20, 30, 40, 50, kwarg1='spam', kwarg2='eggs')

Es gibt diese Ausgabe:

saved_args is {'a': 20, 'args': (30, 40, 50), 'kwargs': {'kwarg1': u'spam', 'kwarg2': u'eggs'}}
saved_args is {'a': 20, 'args': (30, 40, 50), 'kwargs': {'kwarg1': u'spam', 'kwarg2': u'eggs'}}
But locals is now {'a': 20, 'saved_args': {...}, 'args': (30, 40, 50), 'local_var': 10, 'kwargs': {'kwarg1': u'spam', 'kwarg2': u'eggs'}}

Hutspitze: https://stackoverflow.com/a/3137022/2829764

kuzzooroo
quelle
Ab Python 3.5 funktioniert dies nicht mit Standardargumenten, zumindest nicht, wenn sie None sind. Natürlich ist dies ein anderer Fall, bei dem keine * args, ** kwargs verwendet werden.
dasWesen
3
Funktioniert für mich auf Python 3.6.9
Addison Klinke
31

Ich würde nichts tun, aber Sie könnten inspect.getargspecdie Argumente Ihrer Methode überprüfen:

>>> import inspect
>>> def foobar(foo, bar, baz):
...     return inspect.getargspec(foobar)
... 
>>> foobar(1, 2, 3)
ArgSpec(args=['foo', 'bar', 'baz'], varargs=None, keywords=None, defaults=None)

Dies kann dann kombiniert werden locals(), um Ihre Argumente neu zu erstellen :

>>> def foobar(foo, bar, baz):
...     return [locals()[arg] for arg in inspect.getargspec(foobar).args]
... 
>>> foobar(1, 2, 3)
[1, 2, 3]

Sie brauchen solche Magie jedoch wirklich nur, wenn Sie Dekorateure mit erweiterten Funktionen und dergleichen ausführen. Ich denke, es ist übertrieben hier.

Martijn Pieters
quelle
18

Ich bin nicht sicher, ob dies genau das ist, was Sie wollen, aber es locals()bietet ein Wörterbuch mit lokalen Variablen.

>>> def foo(bar, toto):
...     print(locals())
...
>>> foo(3,'sometext')
{'toto': 'sometext', 'bar': 3}
JDG
quelle
14

Ich denke, eine pythonischere Möglichkeit besteht darin, Ihre Funktion in einen Generator zu verwandeln, yieldDaten abzurufen und zu erfassen, solange der Server weiterhin Daten zurückgibt .

Dies sollte zu ordentlichem Code führen und es Ihnen ermöglichen, alle Komplexitäten der Beibehaltung der Argumente über Iterationen hinweg zu umgehen (Python erledigt dies auf magische Weise für Sie :-)).

NPE
quelle
2
Dies ist definitiv einer der Fälle, die für eine solche Anwendung viel besser geeignet sind. Auf diese Weise kann OP auch get_moreMethoden hinzufügen , die Ergebnisse aus der nächsten Reihe von Generatoriterationen zurückgeben.
Tadeck
Die Sache ist, dass es viele Fälle gibt, in denen es Ihnen egal ist, dass mehr Daten vorhanden sind, sodass sich die Rückgabe eines Generators etwas unnatürlich anfühlt. ein next()/ get_more()auf dem zurückgegebenen Objekt zu haben, fühlt sich weitaus angemessener an ...
Kristian Glass
@KristianGlass: Ich bin nicht sicher, ob ich folge. Sie können next()den Generator genauso einfach anrufen (oder nicht anrufen) . stackoverflow.com/questions/4741243/…
NPE
Ja, aber es würde das Antwortobjekt umschließen. Generatoren sind im Wesentlichen Wrapper, ich möchte im Grunde eine Art Generator-y-Mixin
Kristian Glass
9

inspect.getargspecist seit Version 3.0 veraltet. Use signature()and Signature Object, das eine bessere Introspecting-API für Callables bietet.

>>> from inspect import signature
>>> def foo(a, *, b:int, **kwargs):
...     pass

>>> sig = signature(foo)

>>> str(sig)
'(a, *, b:int, **kwargs)'

>>> str(sig.parameters['b'])
'b:int'

>>> sig.parameters['b'].annotation
<class 'int'>
Deutscher Lashevich
quelle
4
import inspect

def f(x, y):
    print(
        inspect.getargvalues(inspect.currentframe())
    )

f(1, 2)

Ergebnis:
ArgInfo(args=['x', 'y'], varargs=None, keywords=None, locals={'y': 2, 'x': 1})

Vitaly Zdanevich
quelle
2
In den Dokumenten heißt es: "Hinweis: Diese Funktion wurde in Python 3.5 versehentlich als veraltet markiert." docs: docs.python.org/3/library/inspect.html
Christopher
4

Ich glaube, die Methode der Wahl stammt getcallargsvon, inspectda sie echte Argumente zurückgibt, mit denen die Funktion aufgerufen wird. Wenn Sie eine Funktion und Argumente und Warnungen an it übergeben ( inspect.getcallargs(func, /, *args, **kwds)), werden die Argumente der realen Methode zurückgegeben, die für den Aufruf verwendet werden, wobei Standardwerte und andere Elemente berücksichtigt werden. Schauen Sie sich unten ein Beispiel an.

from inspect import getcallargs

# we have a function with such signature
def show_params(first, second, third=3):
    pass

# if you wanted to invoke it with such params (you could get them from a decorator as example)
args = [1, 2, 5]
kwargs = {}
print(getcallargs(show_params, *args, **kwargs))
#{'first': 1, 'second': 2, 'third': 5}

# here we didn't specify value for d
args = [1, 2, 3, 4]
kwargs = {}

# ----------------------------------------------------------
# but d has default value =7
def show_params1(first, *second, d = 7):
    pass


print(getcallargs(show_params1, *args, **kwargs))
# it will consider b to be equal to default value 7 as it is in real method invocation
# {'first': 1, 'second': (2, 3, 4), 'd': 7}

# ----------------------------------------------------------
args = [1]
kwargs = {"d": 4}

def show_params2(first, d=3):
    pass


print(getcallargs(show_params2, *args, **kwargs))
#{'first': 1, 'd': 4}

https://docs.python.org/3/library/inspect.html

Alex
quelle
1

kwargs haben keine 'foo', 'bar' oder 'bad' als Schlüssel, also können Sie diese Einträge (mit ihren Werten) zu kwargs hinzufügen und einfach speichern (fetch_data, kwargs).

Scott Hunter
quelle