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

117

我有一个名为 fetch_data 的 Python 函数,它访问远程API获取数据,并将其包装在响应对象中返回。它的代码类似于下面这样:

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

现在,响应数据可能会显示“我有更多的数据,请使用增加的page参数调用我以获取更多数据”。因此,我想要将“方法调用”(函数、参数)存储在响应对象中,这样我就可以使用Response.get_more()来查看存储的函数和参数,然后再次调用该函数,几乎使用相同的参数返回一个新的Response

如果fetch_data被定义为fetch_data(*args, **kwargs),我只需要在response中存储(fetch_data, args, kwargs)。但是,我需要考虑selffoobarbaz - 我可以只存储(fetch_data, foo, bar, baz, kwargs),但那是非常不可取的重复。

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


为什么不将foo,bar,baz和kwargs传递给Response()构造函数,以便稍后调用response.get_more()已经具有这些值? - kurosch
因为那并没有解决问题-如果我改变了fetch_data的签名,我仍然需要更改Response - Kristian Glass
11个回答

168

基本上,我正在尝试弄清楚如何在函数内部获取完全填充的*args和**kwargs,包括函数的命名参数。

不妨在函数开始时通过locals()保存参数?

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

它会生成以下输出:

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

致谢:https://dev59.com/tXA75IYBdhLWcg3wubqn#3137022


从Python 3.5开始,默认参数不支持此操作,至少如果它们为None时是不支持的。当然,这是一个不同的情况,不使用*args,**kwargs。 - dasWesen
5
在Python 3.6.9上对我有效。 - Addison Klinke
2
这是一个很好的答案。但要注意,如果您正在类方法内部工作,则需要“弹出”self参数。(除非您需要它) - calder-ty
1
@calder-ty 根据我的经验,你还需要移除 __class__。在一个类的 __init__ 函数内部,我使用 locals_ = locals().copy()self 弹出,然后使用 locals_.pop("self")。但是仍然会保留 __class__ - Inyoung Kim 김인영

53

虽然我不会这样做,但是你可以使用inspect.signature来检查你的方法所接受的参数:

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

返回的Signature实例具有有序的参数集合(.parameters属性),可以与locals()一起使用,以生成您的参数值列表:
>>> 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]

然而,只有在使用高级函数装饰器等时才需要这种魔法。我认为在这里使用它是过度杀伤力的。

30

我不确定这是否是你想要的,但是locals()提供了一个本地变量字典。

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

15

我认为更符合Python风格的方法是将你的函数变成一个生成器,只要服务器不断返回数据,就可以获取并 yield 数据。

这样可以得到整洁的代码,并使您能够绕过在迭代之间保留参数的所有复杂性 (Python会自动做到这一点:-))


2
这绝对是更适合这种应用的情况之一。而且这种方式还为 OP 提供了机会添加 get_more 方法,以返回下一系列生成器迭代的结果。 - Tadeck
事实上,有很多情况下你并不关心是否有更多的数据,因此返回一个生成器会感觉有点不自然;在返回的对象上使用 next() / get_more() 更加合适... - Kristian Glass
@KristianGlass:我不确定我理解了。您可以在生成器上轻松调用(或不调用)next()。https://dev59.com/5m445IYBdhLWcg3wq8Pp - NPE
是的,但它会包装响应对象;生成器本质上是包装器,我基本上想要一种生成器混合的形式。 - Kristian Glass

12

inspect.getargspec从版本3.0开始已被弃用。请使用signature()和Signature Object替代,它们为可调用的函数提供了更好的内省API。

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

11

我认为选择的方法是使用inspect中的getcallargs,因为它返回函数被调用时使用的真实参数。你可以将一个函数和args以及kwargs传递给它(inspect.getcallargs(func, *args, **kwds)),它将返回真实使用的方法参数,考虑到默认值和其他内容。看下面的例子。

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


7
import inspect

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

f(1, 2)

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


2
根据文档:“注意:这个函数在Python 3.5中被错误地标记为弃用。” 文档链接:https://docs.python.org/3/library/inspect.html - June

1

kwargs不会有'foo'、'bar'或'bad'作为键,因此您可以将这些条目(及其值)添加到kwargs中,并仅存储(fetch_data,kwargs)。


1
我认为最简单/最干净(...呃,"pythonic")的方法就是使用装饰器函数...不需要使用"inspect"或遍历locals() -- 直接从装饰器函数中获取它们。尝试这样做:
def call_info(func: callable):
    def wrapper(*args, **kwargs):
        print(f'Calling {func} with args: {args}, kwargs: {kwargs}')
        return func(*args, **kwargs)
    return wrapper

@call_info
def test_func(name, *args, size: int = 2, cheese: bool = True, **kwargs):
    print('nothing to see here...')
    print(f'but if you wanted to know.... name={name}, args={args}, size=size}, cheese={cheese}, kwargs={kwargs}')

test_func(44)
test_func(name='hello', size=4, cheese=False)

会打印出类似这样的内容:
Calling <function test_func at 0x0000015614079E40> with args: (44,), kwargs: {}
nothing to see here...
but if you wanted to know.... name=44, args=(), size=2, cheese=True, kwargs={}
Calling <function test_func at 0x0000015614079E40> with args: (), kwargs: {'name': 'hello', 'size': 4, 'cheese': False}
nothing to see here...
but if you wanted to know.... name=hello, args=(), size=4, cheese=False, kwargs={}

你可以将@call_info装饰器添加到任何你想查看传递变量的方法/函数中。在主要函数调用之前/之后,你可以对变量进行任何操作...只需修改包装器即可。
这里有一个非常好的链接,描述了如何使用装饰器。

https://realpython.com/primer-on-python-decorators/

也在Python文档中有描述:

https://docs.python.domainunion.de/3/glossary.html#term-decorator


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

1
你的回答可以更好地添加关于代码的功能和如何帮助提问者的更多信息。 - Tyler2P
注意,在 def 语句之后,应该将 locals() 保存在某个变量中。因为如果函数内有其他变量,它们也会在 locals() 中。 - Andrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接