如何确定缺少哪个Python关键字参数?

时间:2014-01-10 21:12:44

标签: python inspect keyword-argument

当忘记将某些参数传递给函数时,Python给出了唯一有用的消息“myfunction()接受X参数(给定Y)”。有没有办法弄清楚缺少的参数的名称,并告诉用户?类似的东西:

try:
    #begin blackbox
    def f(x,y):
        return x*y

    f(x=1)
    #end blackbox
except Exception as e:
    #figure out the missing keyword argument is called "y" and tell the user so

假设开始blackbox和end blackbox之间的代码对于异常处理程序是未知的。

编辑:正如我在下面指出的那样,Python 3已经内置了这个功能。让我扩展一个问题,那么在Python 2.x中是否有一种(可能是丑陋和hacky)的方法呢?

4 个答案:

答案 0 :(得分:3)

更简洁的方法是将函数包装在另一个函数中,通过*args, **kwargs,然后在需要时使用这些值,而不是在事后重新尝试它们。但如果你不想这样做......

在Python 3.x中(非常早期的版本除外),这很容易,正如poke的答案所解释的那样。使用{+ 3}},inspect.signatureinspect.getargvalues以及朋友,可以更轻松地使用3.3+。

在Python 2.x中,没有办法做到这一点。该例外仅在其'f() takes exactly 2 arguments (1 given)'中包含字符args

除了......特别是在CPython 2.x中,它可能有足够的丑陋和脆弱的hackery。

你有一个追溯,所以你有tb_frametb_lineno ......这就是你需要的一切。因此,只要源可用,inspect.Signature.bind_partial模块就可以轻松获取实际的函数调用表达式。然后你只需要解析它(通过inspect)来获取传递的参数,并与函数的签名进行比较(不幸的是,它不像在3.3+中那样容易进入2.x,但是在f.func_defaultsf.func_code.co_argcount等之间,你可以重建它。)

但如果源不可用怎么办?好吧,在tb_frame.f_codetb_lasti之间,您可以找到函数调用在字节码中的位置。 ast模块使得解析相对容易。特别是,在调用之前,关键字参数的位置参数和名称 - 值对都被推送到堆栈上,因此您可以轻松查看推送的名称,以及有多少位置值,并以此方式重新调用函数调用。您可以用同样的方式与签名进行比较。

当然,这取决于有关CPython编译器如何构建字节码的一些假设。只要堆栈最终得到正确的值,以各种不同的顺序执行操作将是完全合法的。所以,它非常脆弱。但我认为没有更好的理由不这样做。

答案 1 :(得分:2)

except TypeError as e:
   import inspect
   got_args = int(re.search("\d+.*(\d+)",str(e)).groups()[0])
   print "missing args:",inspect.getargspec(f).args[got_args:]

更好的方法是装饰器

def arg_decorator(fn):
   def func(*args,**kwargs):
       try: 
           return fn(*args,**kwargs)
       except TypeError:
           arg_spec = inspect.getargspec(fn)
           missing_named = [a for a in arg_spec.args if a not in kwargs]
           if arg_spec.defaults:
                    missing_args = missing_named[len(args): -len(arg_spec.defaults) ]
           else:
                    missing_args = missing_named[len(args):]
           print "Missing:",missing_args
   return func

@arg_decorator
def fn1(x,y,z):                      
      pass

def fn2(x,y):
    pass

arged_fn2 = arg_decorator(fn2)

fn1(5,y=2)
arged_fn2(x=1)

答案 2 :(得分:2)

我认为这样做并没有真正有意义。抛出这样的异常是因为程序员错过了指定参数。因此,如果您故意捕获异常,那么您也可以在第一时间修复它。

话虽如此,在当前的Python 3版本中,正在抛出的TypeError确实提到了调用中缺少哪些参数:

"f() missing 1 required positional argument: 'y'"

不幸的是,参数名称没有单独提及,因此您必须从字符串中提取它:

try:
    f(x=1)
except TypeError as e:
    if 'required positional argument' in e.args[0]:
        argumentNames = e.args[0].split("'")[1::2]
        print('Missing arguments are ' + argumentNames)
    else:
        raise # Re-raise other TypeErrors

正如Joran Beasley在评论中指出的那样,Python 2并没有告诉你哪些参数缺失,而是缺少多少参数。因此无法从异常中判断出调用中缺少哪些参数。

答案 3 :(得分:1)

纯粹处理它的例外是不可能做你想要的并处理关键字参数。这当然是Python 2.7。

在Python中生成此消息的代码是:

PyErr_Format(PyExc_TypeError,
    "%.200s() takes %s %d "
    "argument%s (%d given)",
    PyString_AsString(co->co_name),
    defcount ? "at most" : "exactly",
    co->co_argcount,
    co->co_argcount == 1 ? "" : "s",
    argcount + kwcount);

取自http://hg.python.org/cpython/file/0e5df5b62488/Python/ceval.c

的第3056-3063行

正如您所看到的,关于缺少哪些参数,没有足够的信息给出异常。在此上下文中co是被调用的PyCodeObject。给出的唯一的东西是一个字符串(如果你愿意,你可以解析它)与函数名称,是否存在vararg,预期有多少参数,以及给出了多少参数。正如已经指出的那样,这并没有给出足够的信息来说明没有给出什么参数(在关键字参数的情况下)。

inspect或其他调试模块这样的东西可能能够为你提供足够的信息,告诉你调用了什么函数以及如何调用它,你可以从中找出没有给出的参数。

我还应该提一下,几乎可以肯定,无论你提出什么解决方案都无法处理至少一些扩展模块方法(用C语言编写的方法),因为它们不提供参数信息作为其对象的一部分。