获取函数参数的默认值?

102

对于这个函数

def eat_dog(name, should_digest=True):
    print "ate dog named %s. Digested, too? %" % (name, str(should_digest))

我想要在函数外部读取其参数和附加的默认值。对于这个特定的例子,我想知道name没有默认值(即它是一个必需的参数),而should_digest的默认值为True

我知道inspect.getargspec()可以提供有关参数和默认值的信息,但我看不出两者之间有什么联系:

ArgSpec(args=['name', 'should_digest'], varargs=None, keywords=None, defaults=(True,))

从这个输出中,我如何知道True(在defaults元组中)是should_digest的默认值呢?

此外,我知道“先求得宽恕”模型来解决问题,但不幸的是,该错误的输出不会告诉我缺少的参数的名称:

>>> eat_dog()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: eat_dog() takes at least 1 argument (0 given)

为了提供背景(我为什么想这样做),我正在通过JSON API公开模块中的函数。 如果调用方省略某些函数参数,我希望返回一个指定错误,其中命名省略的特定函数参数。 如果客户端省略了一个参数,但是在函数签名中提供了默认值,则我希望使用该默认值。

7个回答

164

Python3.x

在 Python 3.x 中,你应该使用一个Signature对象:

import inspect

def get_default_args(func):
    signature = inspect.signature(func)
    return {
        k: v.default
        for k, v in signature.parameters.items()
        if v.default is not inspect.Parameter.empty
    }

Python2.x(旧答案)

args/defaults 可以合并为:

import inspect
a = inspect.getargspec(eat_dog)
zip(a.args[-len(a.defaults):],a.defaults)

在这里,a.args[-len(a.defaults):]是具有默认值的参数,显然,a.defaults是相应的默认值。

你甚至可以将zip的输出传递给dict构造函数,并创建适合于关键字解包的映射。


查看文档,此解决方案仅适用于Python2.6或更新版本,因为我假设inspect.getargspec返回一个命名元组。早期版本返回一个常规元组,但很容易进行相应修改。这是一个适用于旧(和新)版本的版本:

import inspect
def get_default_args(func):
    """
    returns a dictionary of arg_name:default_values for the input function
    """
    args, varargs, keywords, defaults = inspect.getargspec(func)
    return dict(zip(args[-len(defaults):], defaults))

说起来:

    return dict(zip(reversed(args), reversed(defaults)))

这也可以工作,对一些人可能更易理解。



@Tadeck -- 我必须点赞你的回答。在看到你的帖子之前,我只是猜测它们如何结合。你的回答证实了我的猜想。 - mgilson
@Tadeck,请问您能解释一下这是如何工作的吗?我有点困惑其中的意思。 - skyler
@skyler -- 你知道zip函数吗?有哪部分不理解吗?如果你能告诉我哪里让你困惑,我很乐意改进答案。 - mgilson
1
@mgilson 我现在明白了。具有默认值的参数不能与没有默认值的参数交错使用,因此将默认值映射到参数列表时,从最后一个参数开始向后移动,可以正确地连接两者。 - skyler
@skyler -- 恰恰如此。我自己也无法更好地表述它。 - mgilson

13

根据您的需求不同,您可能不需要使用inspect模块,因为您可以检查函数的__defaults__属性:

>>> eat_dog.__defaults__
(True,)
>>> eat_dog.__code__.co_argcount
2
>>> eat_dog.__code__.co_varnames
('name', 'should_digest')
>>> 
>>> eat_dog.__kwdefaults__
>>> eat_dog.__code__.co_kwonlyargcount
0 

从这个代码中,你怎么知道默认的 True 对应哪个参数? - lucidbrot

10
mgilson的回答中提供了一个获取特定默认参数版本的方法。
value = signature(my_func).parameters['param_name'].default

以下是完整的工作版本,使用Python 3.8.2制作

from inspect import signature

def my_func(a, b, c, param_name='apple'):
    pass

value = signature(my_func).parameters['param_name'].default

print(value == 'apple') # True


2
您可以通过一些其他帖子提到的双下划线变量来获取此内容。将其放入一个简单的辅助函数中,即可获得默认值的字典。
•.__code__.co_varnames: 所有输入变量的元组 •.__defaults__: 默认值的元组 - 值得注意的是,该元组仅包括默认提供的变量,这些变量必须始终位于函数参数的最后位置。
您可以使用这两个项目将.__code__.co_varnames中的最后n个变量与.__defaults__中的所有项目进行匹配。
编辑感谢@griloHBG - 添加if语句以防止未指定默认值时出现异常。
def my_fn(a, b=2, c='a'):
    pass

def get_defaults(fn):
    if fn.__defaults__==None:
        return {}
    return dict(zip(
        fn.__code__.co_varnames[-len(fn.__defaults__):],
        fn.__defaults__
    ))

print(get_defaults(my_fn))

Should give:

{'b': 2, 'c': 'a'}

2
为什么要减1,没有解释? - conmak
2
__defaults__ 是一个字符串列表,其长度等于具有默认值的参数数量。由于在 Python 中,具有默认值的参数必须始终是最后一个参数,因此使用 -__defaults__len(带有 : 以获取到数组末尾)将显示具有默认值的参数名称(来自 co_varnames)。但是,如果 fn 没有带有默认值的参数,则函数 get_defaults 将失败。检查 __defaults__len 是否为零将有助于防止错误:如果 __defaults__len 为零,则返回。 - griloHBG
谢谢。虽然如果您需要捕获kwargs的其余部分会有问题。将在右侧添加填充。def my_fn(a, b=2, c='a', **others): - nggit

2

为了处理仅关键字参数(并且因为默认值和kwonlydefaults可以为None):

spec = inspect.getfullargspec(func)
defaults = dict(zip(spec.args[::-1], (spec.defaults or ())[::-1]))
defaults.update(spec.kwonlydefaults or {})

没错,感谢指出这一点。我的原始答案是在Python3.x并不那么流行之前编写的。现在有一个更好的方法来处理这个问题,使用Signature对象(我已经将其添加到我的原始答案中)。 - mgilson

0
在Python中,所有带有默认值的参数都应该放在没有默认值的参数之后。因此,映射应该从末尾开始,直到你用完默认值列表为止。因此,这就是逻辑:
dict(zip(reversed(args), reversed(defaults)))

返回正确映射的默认值。


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