当忘记将某些参数传递给函数时,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)的方法呢?
答案 0 :(得分:3)
更简洁的方法是将函数包装在另一个函数中,通过*args, **kwargs
,然后在需要时使用这些值,而不是在事后重新尝试它们。但如果你不想这样做......
在Python 3.x中(非常早期的版本除外),这很容易,正如poke的答案所解释的那样。使用{+ 3}},inspect.signature
和inspect.getargvalues
以及朋友,可以更轻松地使用3.3+。
在Python 2.x中,没有办法做到这一点。该例外仅在其'f() takes exactly 2 arguments (1 given)'
中包含字符args
。
除了......特别是在CPython 2.x中,它可能有足够的丑陋和脆弱的hackery。
你有一个追溯,所以你有tb_frame
和tb_lineno
......这就是你需要的一切。因此,只要源可用,inspect.Signature.bind_partial
模块就可以轻松获取实际的函数调用表达式。然后你只需要解析它(通过inspect
)来获取传递的参数,并与函数的签名进行比较(不幸的是,它不像在3.3+中那样容易进入2.x,但是在f.func_defaults
,f.func_code.co_argcount
等之间,你可以重建它。)
但如果源不可用怎么办?好吧,在tb_frame.f_code
和tb_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语言编写的方法),因为它们不提供参数信息作为其对象的一部分。