循环中异常处理程序的更好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()
但我不喜欢这种方法,因为它会卷走循环。callable
t
无法使用
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循环是如何为您工作的?编辑:没关系。让我再读一遍你的新答案。