如何使用循环内部管理器?

时间:2015-04-17 20:06:31

标签: python contextmanager

我想要这样的事情:

from contextlib import contextmanager

@contextmanager
def loop(seq):
    for i in seq:
        try:
            do_setup(i)
            yield # with body executes here
            do_cleanup(i)
        except CustomError as e:
            print(e)

with loop([1,2,3]):
    do_something_else()
    do_whatever()

但是contextmanager不起作用,因为它期望生成器只生成一次。

我想要这个的原因是因为我基本上想要制作自己的自定义循环。我有一个改进的IPython,用于控制测试设备。它显然是一个完整的Python REPL,但大多数时候用户只是调用预定义的函数(类似于Bash提示符),并且用户不应该是程序员或熟悉Python。需要有一种方法可以使用每次迭代的setup / cleanup和异常处理来循环一些任意代码,并且它应该与上面的语句一样简单。

2 个答案:

答案 0 :(得分:11)

我认为发电机在这里效果更好:

def loop(seq):
    for i in seq:
        try:
            print('before')
            yield i  # with body executes here
            print('after')
        except CustomError as e:
            print(e)

for i in loop([1,2,3]):
    print(i)
    print('code')

会给:

before
1
code
after
before
2
code
after
before
3
code
after

Python仅进入和退出with块一次,因此您无法在重复执行的输入/退出步骤中使用逻辑。

答案 1 :(得分:2)

一个更完整的答案,如果异常可能发生在生成器之外:

from contextlib import contextmanager

class CustomError(RuntimeError):
    pass

@contextmanager
def handle_custom_error():
    try:
        yield
    except CustomError as e:
        print(f"handled: {e}")

def loop(seq):
    for i in seq:
        try:
            print('before')
            if i == 0:
                raise CustomError("inside generator")
            yield i # for body executes here
            print('after')
        except CustomError as e:
            print(f"handled: {e}")

@handle_custom_error()
def do_stuff(i):
    if i == 1:
        raise CustomError("inside do_stuff")
    print(f"i = {i}")

for i in loop(range(3)):
    do_stuff(i)

输出:

before
handled: inside generator
before
handled: inside do_stuff
after
before
i = 2
after