将关键字参数传递给使用位置参数定义的函数时,会产生误导(?)TypeError

时间:2010-02-01 20:10:31

标签: python arguments

在cPython 2.4中:

def f(a,b,c,d):
    pass

>>> f(b=1,c=1,d=1)
TypeError: f() takes exactly 4 non-keyword arguments (0 given)

但:

>>> f(a=1,b=1,c=1)
TypeError: f() takes exactly 4 non-keyword arguments (3 given)

显然,我真的非常了解Python的函数参数处理机制。有人愿意分享一下这个吗?我看到发生了什么(比如填充参数插槽,然后放弃),但我认为这会导致一个新手。

(另外,如果人们有更好的问题关键词 - 类似“胆量” - 请重新加入)

2 个答案:

答案 0 :(得分:13)

当你说

def f(a,b,c,d):

你告诉python f需要4个位置参数。每次拨打f时,必须提供4个参数,第一个值将分配给a,第二个值将分配给b等。

您可以使用类似

的内容来致电f

f(1,2,3,4)f(a=1,b=2,c=3,d=4),甚至f(c=3,b=2,a=1,d=4)

但在所有情况下,必须提供4个参数。

f(b=1,c=1,d=1)返回错误,因为没有为a提供任何值。 (0给出) f(a=1,b=1,c=1)返回错误,因为没有为d提供任何值。 (3给出)

给出的args数表示在实现错误之前python已经达到了多远。

顺便说一下,如果你说

def f(a=1,b=2,c=3,d=4):

然后你告诉python f需要4个可选参数。如果未给出某个arg,则会自动为您提供其默认值。然后你可以通过调用

逃脱

f(a=1,b=1,c=1)f(b=1,c=1,d=1)

答案 1 :(得分:0)

理论上,可以用更清晰,更丰富的内容包装生成的TypeError。但是,有许多小细节,其中一些我不知道如何解决。

注意:以下代码是一个勉强工作的示例,而不是一个完整的解决方案。

try:
    fn(**data)
except TypeError as e:
    ## More-sane-than-default processing of a case `parameter ... was not specified`
    ## XXX: catch only top-level exceptions somehow?
    ##  * through traceback?
    if fn.func_code.co_flags & 0x04:  ## XXX: check
        # it accepts `*ar`, so not the case
        raise
    f_vars = fn.func_code.co_varnames
    f_defvars_count = len(fn.func_defaults)
    ## XXX: is there a better way?
    ##  * it catches `self` in a bound method as required. (also, classmethods?)
    ##  * `inspect.getargspec`? Imprecise, too (for positional args)
    ##  * also catches `**kwargs`.
    f_posvars = f_vars[:-f_defvars_count]
    extra_args = list(set(data.keys()) - set(f_vars))
    missing_args = list(set(f_posvars) - set(data.keys()))
    if missing_args:  # is the case, raise it verbosely.
        msg = "Required argument(s) not specified: %s" % (
          ', '.join(missing_args),)
        if extra_args:
            msg += "; additionally, there are extraneous arguments: %s" % (
              ', '.join(extra_args))
        raise TypeError(msg, e)
        #_log.error(msg)
        #raise
    raise