我正在创建一个装饰器,它在其目标函数中捕获一个引发错误,并允许用户继续执行脚本(绕过该函数)或退出脚本。
def catch_error(func):
"""
This decorator is used to make sure that if a decorated function breaks
in the execution of a script, the script doesn't automatically crash.
Instead, it gives you the choice to continue or gracefully exit.
"""
def caught(*args):
try:
return func(*args)
except Exception as err:
question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
answer = raw_input(question)
if answer.lower() in ['yes','y']:
pass
else:
print " Aborting! Error that caused failure:\n"
raise err
return None
return caught
请注意,如果用户选择绕过错误返回功能并继续执行脚本,则装饰器将返回None。这适用于仅返回单个值的函数,但它会在尝试解压缩多个值的函数上崩溃。例如,
# Both function and decorator return single value, so works fine
one_val = decorator_works_for_this_func()
# Function nominally returns two values, but decorator only one, so this breaks script
one_val, two_val = decorator_doesnt_work_for_this_func()
有没有办法可以确定我的目标函数应该返回的值的数量?例如,像:
def better_catch_error(func):
def caught(*args):
try:
return func(*args)
except Exception as err:
...
num_rvals = determine_num_rvals(func)
if num_rvals > 1:
return [ None for count in range(num_rvals) ]
else:
return None
return caught
与往常一样,如果有更好的方法来做这类事情,请告诉我。谢谢!
更新:
感谢所有建议。我决定将catch_error的范围缩小到单个类函数,它只返回一个字符串值。我只是将返回多个值的所有函数拆分为单独的函数,这些函数返回单个值以使它们兼容。我一直希望让catch_error更通用(并且有一些有用的建议如何做到这一点),但对于我的应用程序来说,它有点矫枉过正。再次感谢。
答案 0 :(得分:3)
不,你无法确定这一点。
Python是一种动态语言,根据输入和其他因素,给定的函数可以返回任意大小的任意序列或根本不返回任何序列。
例如:
import random
def foo():
if random.random() < 0.5:
return ('spam', 'eggs')
return None
此函数将返回一半的时间元组,但另一半返回None
,因此Python无法告诉您foo()
将返回的内容。
你的装饰器还有很多方法可以失败,顺便说一句,不仅仅是当函数返回一个调用者然后解压缩的序列时。如果调用者期望字典并开始尝试访问密钥或列表并想知道长度,该怎么办?
答案 1 :(得分:3)
你的装饰者无法预测你的函数会返回什么,但没有什么能阻止你告诉装饰者什么样的返回签名模拟:
@catch_error([None, None])
def tuple_returner(n):
raise Exception
return [2, 3]
您的装饰器将返回其参数(None
),而不是返回[None, None]
。
编写一个参数化装饰器有点棘手:将评估表达式catch_error([None, None])
,并且必须返回将应用于修饰函数的实际装饰器。它看起来像这样:
def catch_error(signature=None):
def _decorator(func):
def caught(*args):
try:
return func(*args)
except Exception as err:
# Interactive code suppressed
return signature
return caught
return _decorator
请注意,即使您只是希望它返回None
,您也需要执行一次:
@catch_error()
def some_function(x):
...
答案 2 :(得分:2)
Martijn Pieters的回答是正确的,这是the Halting Problem
的特定情况但是,您可以通过将错误返回值传递给装饰器来解决它。像这样:
def catch_error(err_val):
def wrapper(func):
def caught(*args):
try:
return func(*args)
except Exception as err:
question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
answer = raw_input(question)
if answer.lower() in ['yes','y']:
pass
else:
print " Aborting! Error that caused failure:\n"
raise err
return err_val
return caught
return wrapper
然后你可以使用:
进行装饰@catch_error({})
def returns_a_dict(*args, **kwargs):
return {'a': 'foo', 'b': 'bar'}
另外作为一种风格,如果你在包装函数中抓取*args
,你应该抓住**kwargs
,这样你就可以正确地包装带有关键字参数的函数。否则,如果您拨打wrapped_function(something=value)
最后,作为另一种风格,看到用{else}做if a: pass
的代码是令人困惑的。在这些情况下尝试使用if !a
。所以最后的代码:
def catch_error(err_val):
def wrapper(func):
def caught(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as err:
question = '\n{0} failed. Continue? (yes/no): '.format(func.func_name)
answer = raw_input(question)
if answer.lower() not in ['yes','y']:
print " Aborting! Error that caused failure:\n"
raise err
return err_val
return caught
return wrapper