当向使用位置参数定义的函数传递关键字参数时,会出现误导性类型错误。

8

在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的函数参数处理机制。 有人可以分享一些见解吗? 我知道正在发生什么(类似于填充参数槽,然后放弃),但我认为这会使新手困惑。

(此外,如果有更好的问题关键字-例如“内部机制”-请重新标记)


只是出于好奇,您在给出的这两个示例中,您期望Python做什么? - Will McCutchen
我期望它会给两个都说3。 - Gregg Lind
2个回答

13

当你说

def f(a,b,c,d):
你告诉 Python 函数 f 需要 4 个位置参数。每次调用 f 函数时,必须精确地提供 4 个参数,并且第一个值将被分配给 a,第二个值将被分配给 b,以此类推。
你可以使用以下任一形式调用函数 ff(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 有四个可选参数。如果某个参数没有被提供,那么它的默认值会自动为您提供。然后您可以直接调用函数:

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


所以,这正是不明显的部分:给定的参数数量表示Python在意识到错误之前走了多远。我会将其归类为“可能会咬到新手的瑕疵”,因为实际上,在两种情况下都给出了3个参数。 - Gregg Lind
做一个调用包装器来捕获这种错误并引发更合适的错误怎么样?我尝试过这样做,但有很多小问题需要解决;还有其他人尝试过吗?(另外,我想知道为什么默认的CPython没有这样做) - HoverHell
关键在于给定的参数数量表示 Python 在意识到错误之前执行了多少行代码。 - Tommy

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

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