获取传递给函数的所有参数和值

我有一个 Python 函数 fetch_data,它访问远程 API,获取一些数据,并将其包装在响应对象中返回。它看起来有点像下面这些:

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

现在,响应数据可能会说“我有更多的数据,使用增量的 page参数调用我以获得更多的数据”。因此,我实际上想要在响应对象中存储“ The Method Call”(函数,参数) ,这样我就可以有一个 Response.get_more()来查看存储的函数和参数,然后用(几乎)相同的参数再次调用函数,返回一个新的 Response

现在,如果 fetch_data被定义为 fetch_data(*args, **kwargs),我可以只在 response中存储 (fetch_data, args, kwargs)。然而,我有 selffoobarbaz担心-我可以只存储 (fetch_data, foo, bar, baz, kwargs),但这是一个非常不理想的重复数量。

实际上,我正在尝试解决如何从函数内部获得完全填充的 *args**kwargs,包括函数的命名参数。

147958 次浏览

I think a more Pythonic way is to turn your function into a generator, fetching and yielding data for as long as the server keeps returning stuff.

This should result in neat code and would enable you to side-step all of the complexities of preserving the arguments across iterations (Python will magically do it for you :-))

kwargs won't have 'foo', 'bar' or 'bad' as keys, so you can add those entries (w/ their values) to kwargs and just store (fetch_data, kwargs).

Not something I'd do, but you could use inspect.signature to introspect the arguments your method takes:

>>> import inspect
>>> def foobar(foo, bar, baz):
...     return inspect.signature(foobar)
...
>>> foobar(1, 2, 3)
<Signature (foo, bar, baz)>

The returned Signature instance has an ordered collection of parameters (the .parameters attribute) which can then be used together with locals() to produce a list of your argument values:

>>> def foobar(foo, bar, baz):
...     sig, foobar_locals = inspect.signature(foobar), locals()
...     return [foobar_locals[param.name] for param in sig.parameters.values()]
...
>>> foobar(1, 2, 3)
[1, 2, 3]

However, you really only need such magic when doing advanced function decorators and the like. I think it's overkill here.

Essentially, I'm trying to work out how to, from within a function, get a completely populated *args and **kwargs, including the function's named parameters.

How about saving the arguments via locals() at the beginning of the function?

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

It gives this output:

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

Hat tip: https://stackoverflow.com/a/3137022/2829764

inspect.getargspec is deprecated since version 3.0. Use signature() and Signature Object, which provide a better introspecting API for callables.

>>> 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'>
import inspect


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


f(1, 2)

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

I am not sure this is exactly what you want, but locals() provides a dictionary of local variables.

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

I believe the method to choose is getcallargs from inspect as it returns real arguments with which function will be invoked. You pass a function and args and kwargs to it (inspect.getcallargs(func, *args, **kwds)), it will return real method's arguments used for invocation, taking into consideration default values and other stuff. Have a look at an example below.

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

Try this:

def my_func(a, *args, **kwargs):
v = locals()
allargs = [v[i] for i in my_func.__code__.co_varnames if i in v and i not in ('args','kwargs')]
allargs.extend(args)
return allargs, kwargs

List allargs combines required and optional arguments. The code has to be placed at the beginning of function, at least v = locals().

>>> def fun(a,b,c):
...   print(locals())
...
>>> fun(1,2,3)
{'a': 1, 'b': 2, 'c': 3}