循环中异常处理程序的更好python模式?
我发现自己在测试中经常使用以下模式:循环中异常处理程序的更好python模式?,python,Python,我发现自己在测试中经常使用以下模式: def test(params): e_list = [] for p in params: try: run_test(p) # Or a block of codes that can continue or break except Exception as e: e_list.append(e) assert isEmpty(e_list), 'e
def test(params):
e_list = []
for p in params:
try:
run_test(p) # Or a block of codes that can continue or break
except Exception as e:
e_list.append(e)
assert isEmpty(e_list), 'error encountered: {}'.format(e_list)
我发现自己经常重写这个模式,尤其是循环的长代码块,它有一些流控制,包括continue
和break
。我想知道这个模式是否有python ic包装
我考虑过这样的包装函数:
def assert_all_tests(test_list):
e_list = []
for t in test_list:
try:
t()
except Exception as e:
e_list.append(e)
assert isEmpty(e_list), 'error encountered: {}'.format(e_list)
def test(params):
assert_all_tests([functools.partial(run_test, p) for p in params])
def test(params):
ErrorHandler.clearErrorList()
for p in params:
with ErrorHandler():
run_test(p) # or code block that can continue or break
ErrorHandler.assertEmptyErrorList()
但我不喜欢这种方法,因为它会卷走循环。callablet
无法使用continue
或break
对循环进行流控制(不再有循环,只有列表理解)
另一种方法是使用如下上下文类:
def assert_all_tests(test_list):
e_list = []
for t in test_list:
try:
t()
except Exception as e:
e_list.append(e)
assert isEmpty(e_list), 'error encountered: {}'.format(e_list)
def test(params):
assert_all_tests([functools.partial(run_test, p) for p in params])
def test(params):
ErrorHandler.clearErrorList()
for p in params:
with ErrorHandler():
run_test(p) # or code block that can continue or break
ErrorHandler.assertEmptyErrorList()
其中,ErrorHandler
将是一个具有相应的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
的类,并在类变量中保留一个错误列表。但是我觉得在test
功能级别,这并不比原始模式简单:因为ErrorHandler
实例无法知道循环何时开始和结束,所以我仍然必须编写循环前和循环后的固定装置
我想听听围绕这个模式的方法。谢谢
编辑
谢谢大家的评论
受@paul cornelius答案启发的新方法
class ResultCollector(object):
def __init__(self, raise_on_error=True):
self.result_list = []
self.raise_on_error = raise_on_error
def do(self, func, *args, **kwds):
'''do can only deal with code block that can be wrapped into a function'''
try:
return func(*args, **kwds)
except Exception as e:
if not isinstance(e, AssertionError) and self.raise_on_error:
raise
self.result_list.append(e.message or e)
else:
self.result_list.append(None)
def assertClean(self):
assert not [x for x in self.result_list if x is not None], 'test results: {}'.format(self.result_list)
def __enter__(self):
self.result_list = []
return self
def __exit__(self, exc_t, exc_i, exc_tb):
if exc_t:
return None
self.assertClean()
return True
def test():
def can_be_refactored_into_func(p):
assert p%3, 'failed {}'.format(p)
def condition_for_skip(p):
return p%2
def condition_for_break(p):
return p>5
with ResultCollector() as rc:
for p in range(10):
if condition_for_skip(p):
rc.result_list.append('skipped {}'.format(p))
continue
if condition_for_break(p):
rc.result_list.append('ended {}'.format(p))
break
rc.do(can_be_refactored_into_func, p)
当循环块中的代码可以划分为上述函数时,它工作得非常好。一个只做一件事的小类怎么样
class TestTracker:
def __init__(self):
self.error_list = []
def do_test(self, f, p):
try:
f(p)
except Exception as e:
self.error_list.append(e)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_value is not None:
self.error_list.append(exc_value)
return True
def test(params):
tt = TestTracker()
for p in params:
tt.do_test(run_test, p)
assert isEmpty(tt.error_list), 'error encountered: {}'.format(tt.error_list)
def test2(params):
tt = TestTracker()
for p in params:
with tt:
# a block of code with loop control statements
pass
assert isEmpty(tt.error_list), 'error encountered: {}'.format(tt.error_list)
我修改了这个答案,使这个类成为上下文管理器。test2
展示了如何将其与循环控制语句一起使用。如果上下文中未引发异常,\uuuuu exit\uuuu
的参数将为无
您甚至可以将与语句和对do\u test
的调用混合使用
Python可以做任何事情
编辑:
为TestTracker添加一些便利
class TestTracker:
def __init__(self):
self.error_list = []
def do_test(self, f, p):
try:
f(p)
except Exception as e:
self.error_list.append(e)
def __bool__(self):
return len(self.error_list) == 0
def __str__(self):
return 'error encountered: {}'.format(self.error_list)
def test(params):
tt = TestTracker()
for p in params:
tt.do_test(run_test, p)
assert tt, str(tt)
为什么要在列表中收集错误而不是抛出遇到的第一个错误?因为有些测试我们希望看到将抛出的所有错误的列表,而不是第一个错误,因为我们决定修复它的操作过程。当我有了所有参数的测试结果时,它有时会告诉我潜在的问题在哪里。你是对的,大多数时候我们只是想重播。但是这个模式是需要上下文的。你不能使用一个普通的函数,它接受函数和参数列表,并用所有的逻辑进行循环吗?好的,谢谢。我不确定这是否有帮助,但您是否考虑过将循环放置在上下文管理器中,并让ErrorhHandler
处理exit
时抛出和收集的错误。您可以让runTest
引发自定义BreakException
s和ContinueException
s,这是for
循环中的try
语句正确解释的:除了BreakException:break
,等等。这很好,但不能解决测试块无法轻松包装到run\u test
函数中的问题,因为它包含循环流控制语句。顺便说一句,你也可以在TestTracker.\uuu del\uuuu
中执行assert
,这样当局部变量超出范围时它会自动断言。我不喜欢将assert语句放在\uu del\uuu
中的想法。该函数不会在名称超出范围时运行,而是在对象即将销毁时运行。不一样,原则上不受程序员的直接控制。另外(从文档中):“不能保证当解释器退出时仍然存在的对象会调用\uu del\uu()
方法。”经过思考,我同意。我想我们可以在\uuuu exit\uuuu
上断言,这样上面的模式就变成,TestTracker()在参数中作为tt:for p:…
我修改了我的答案以支持上下文管理器。这应该适用于包含continue
和break
的代码块,正如我写的那样,上下文管理器进入循环,包装一块测试代码,不是像在你的评论中那样在外部。但是在每次循环迭代中没有创建一个新的TestTracker
实例,因此会出现一个黑色的错误列表
?CM in循环是如何为您工作的?编辑:没关系。让我再读一遍你的新答案。