通过`for`循环内的生成器捕获异常

时间:2014-02-13 04:37:30

标签: python exception-handling generator

我想为生成器编写装饰器,它将捕获for循环中的所有异常,处理它们并继续循环。

我写了这个装饰器(用于Django 1.5 ORM):

def savepoint_loop(generator, uniq_error_in='_uniq'):
    with commit_on_success():
        sp = savepoint()
        for obj in generator:
            try:
                yield obj
            except DatabaseError as e:
                if uniq_error_in not in e.args[0]:
                    raise
                savepoint_rollback(sp)
                yield None
            else:
                savepoint_commit(sp)
                sp = savepoint()

我用它像:

loop = savepoint_loop(offer.booking_data.iteritems())
for provider_name, booking_data in loop:
    try:
        BookingData.objects.create(
            offer=pnr_offer, provider=provider_name, **booking_data)
    except Exception as e:
        loop.throw(e)

但它看起来并不像Pythonic。它允许我让我的代码干,但看起来很混乱。 有没有办法让它更干净?至少我想删除try-except-throw构造或将其更改为with运算符。

理想情况下,它应如下所示:

for provider_name, booking_data in savepoint_loop(
        offer.booking_data.iteritems()):
    BookingData.objects.create(
         offer=pnr_offer, provider=provider_name, **booking_data)

2 个答案:

答案 0 :(得分:1)

import contextlib

@contextlib.contextmanager
def error_processor(uniq_error_in='_uniq'):
    sp = savepoint()
    try:
        yield
    except DatabaseError as e:
        if uniq_error_in not in e.args[0]:
            raise
        savepoint_rollback(sp)
    else:
        savepoint_commit(sp)

这是一个上下文管理器,应该完成你的协同工作,但希望以一种更容易理解的方式。您可以按如下方式使用它:

with commit_on_success():
    for provider_name, booking_data in offer.booking_data.iteritems():
        with error_processor():
            BookingData.objects.create(
                offer=pnr_offer, provider=provider_name, **booking_data)

我无法将commit_on_success放入上下文管理器,因为commit_on_success需要绕过for循环,但错误处理需要进入循环内部。 / p>

答案 1 :(得分:0)

嗯......我想我明白为什么这不是直截了当的。迭代上的任何循环基本上包括以下步骤:

  1. 初始化
  2. 检查终止条件
  3. 获取下一个元素
  4. 执行循环体
  5. ...重复步骤2-4直到满足终止条件
  6. 在Python for循环中,步骤2和3封装在for x in y语句中。但听起来你想使用相同的try...except来捕获步骤3和4中的异常。所以看起来这将要求你“分解”for语句,即使用手动实现它改为while循环。

    iterable = offer.booking_data.iteritems() # step 2
    try:
        while True:
            try:
                provider_name, booking_data = iterable.next() # step 3
                BookingData.objects.create(...) # step 4
            except:
    except StopIteration:
        pass
    

    我确信你会同意这在风格上比你在问题中已有的代码示例更糟糕,try...except循环中有for。当然,如果你真的需要步骤3和4进入同一个try块,可以想象它可能是有用的,但我想这样的情况很少见。

    也有可能有一些偷偷摸摸的方式来构建一个能够满足你想要的发电机,但我想不出一个。