如何模拟内置'意想不到的kwarg'错误?

时间:2016-12-11 16:14:26

标签: python

假设函数query的定义如下:

def query(how='Here or there'):
    print 'Would you like them\n%s?' % how

然后,当(无效)表达式

query(how='With a mouse', who='Daniel', where='Anywhere')

被评估,Python自动发出错误

Traceback (most recent call last):
  File "/tmp/seuss.py", line 4, in <module>
    query(how='With a mouse', who='Daniel', where='Anywhere')
TypeError: query() got an unexpected keyword argument 'who'

现在,假设我需要定义一个带有零个或多个位置参数的函数reply,以及可选的命名参数prefix。 IOW,下面的所有表达式都应该是有效的,至少在语法上是这样的:

reply('in a box', 'with a fox')
reply('In a house', 'With a mouse', preamble='I would not like them\n')
reply(preamble='I do not like them, Sam-i-am.')

我知道实现这样一个功能的唯一方法是使用签名(*args, **kwargs);不幸的是,有了这个签名,就reply('green eggs', wot='and', wotelse='ham')而言,无效的表达式就Python来说都很好,这意味着我们必须自己进行错误检查。

由于这种错误检查非常公式化且乏味,因此将其重构为自己独立的模块是有意义的。例如 1

# check.py

def unexpected_kwargs(kwargs):
    unexpected = kwargs.keys()
    if len(unexpected) > 0:
        import inspect
        caller = inspect.getframeinfo(inspect.currentframe(1)).function
        raise TypeError("%s() got an unexpected keyword argument '%s'" %
                        (caller, unexpected[0]))

编辑:为了解决这个问题,请将错误处理代码的因子分解为给定

现在我们可以像这样实现reply(*args, **kwargs)

import check

def reply(*args, **kwargs):

    preamble = kwargs.pop('preamble', 'Not ')
    check.unexpected_kwargs(kwargs)

    # rest of function definition

... reply('green eggs', wot='and', wotelse='ham')的评估结果为

Traceback (most recent call last):
  File "/tmp/seuss.py", line 14, in <module>
    reply('green eggs', wot='and', wotelse='ham')
  File "/tmp/seuss.py", line 9, in reply
    check.unexpected_kwargs(kwargs)
  File "/tmp/check.py", line 8, in unexpected_kwargs
    (frameinfo.function, unexpected[0]))
TypeError: reply() got an unexpected keyword argument 'wot'

这看起来类似于前面显示的错误输出,但请注意,在该输出中,违规行(query(how='With a mouse', who='Daniel', where='Anywhere'))在回溯的最后打印,而在第二个输出中,打印出几个错误上面的线。在违规行之后,其余的追溯是IMO不必要的噪音,只会掩盖导致失败的错误。

IOW,真的复制Python对意外关键字的处理,错误输出应如下所示:

Traceback (most recent call last):
  File "/tmp/seuss.py", line 14, in <module>
    reply('green eggs', wot='and', wotelse='ham')
TypeError: reply() got an unexpected keyword argument 'wot'

当然,实现这一目标的一种错误方法是:

def unexpected_kwargs_NO_GOOD(kwargs):
    unexpected = kwargs.keys()
    if len(unexpected) > 0:
        import inspect
        import traceback
        import sys

        print >> sys.stderr, ('Traceback (most recent call last):\n%s' %
                              ''.join(traceback.format_stack()[:-2])),

        frameinfo = inspect.getframeinfo(inspect.currentframe(1))

        print >> sys.stderr, ('TypeError: '
                              "%s() got an unexpected keyword argument '%s'" %
                              (frameinfo.function, unexpected[0]))
        sys.exit(1)

这会产生正确的输出,但它不会引发上游代码可以捕获的异常。相反,它无条件地终止程序的执行。

有没有办法获得所需的输出,同时提出适当的异常?

1 可以分解出更多的参数处理而不仅仅是错误处理,但由于这个附加功能与帖子的问题相关,所以我把它保留了上面的示例代码。

3 个答案:

答案 0 :(得分:2)

如果您已经确切地知道要接受哪些关键字参数,那么您根本不想使用**kwargs。您只想使用普通的旧关键字参数。

您似乎正在搜索在Python 3中添加的功能(请参阅PEP 3102)以支持“仅关键字”参数:永远不会被位置参数自动填充的参数。< / p>

>>> def reply(*args, preamble='Not '):
...     print(args)
...     print(preamble)
...     
>>> reply('arg1', 'arg2')
('arg1', 'arg2')
Not 
>>> reply('arg1', 'arg2', preamble='yeah')
('arg1', 'arg2')
yeah
>>> reply('arg1', 'arg2', preamble='yeah', wot='wat')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-b16c25a18626> in <module>()
----> 1 reply('arg1', 'arg2', preamble='yeah', wot='wat')

TypeError: reply() got an unexpected keyword argument 'wot

Python 2中不可能提供您想要的内容.Python 2代码中的常用模式只是使用**kwargs,而清楚地记录文档字符串中接受的选项。通常我们只是忽略发送的任何虚假参数,它们不会引发错误。

答案 1 :(得分:1)

你不能在Python引发它的同一点上引发异常,不。当有更多的关键字参数可以处理时,只需引发一个异常。

应该没事;所有调试错误函数调用的信息仍然存在于回溯中。

答案 2 :(得分:0)

我不赞成所有这些原型检查和参数魔法,因为它违反了KISS原则,但我认为有一种方法可以实现(几乎)你想要的确切行为:

def reply(*args, **kwargs):
    try:
        my_arg_check(args, kwargs, *other_parameters)
    except MyArgCheckFailure as exc:
        raise TypeError(exc.my_custom_message)