传递关键字参数给使用位置参数定义的函数时的误导性(?)类型错误

8 投票
2 回答
5813 浏览
提问于 2025-04-15 18:45

在 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

从理论上讲,可以把产生的类型错误(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
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个)

给出的参数数量显示了Python在发现错误之前处理到了哪一步。

顺便说一下,如果你说

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

那么你是在告诉Python,f这个函数需要4个可选参数。如果某个参数没有提供,Python会自动给你一个默认值。这样你就可以像这样调用:

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

撰写回答