在 python 中尝试使用生成器最终阻止

Try finally block with generator in python

谁能给我解释一下这段代码中 generatortry except 的概念:

from contextlib import contextmanager
 
@contextmanager
def file_open(path):
    try:
        f_obj = open(path, 'w')
        yield f_obj
    except OSError:
        print("We had an error!")
    finally:
        print('Closing file')
        f_obj.close()
 
 
if __name__ == '__main__':
    with file_open('test.txt') as fobj:
        fobj.write('Testing context managers')

据我所知,不管 try 中的表达式是否正确,finally 总是被执行。所以在我看来,这段代码应该像这样工作:如果我们没有异常,我们打开文件,转到生成器,然后我们转到 finally 块和函数中的 return。但我无法理解 generator 在此代码中的工作原理。我们只使用了一次,这就是为什么我们不能在文件中写入所有文本。但我认为我的想法是不正确的。为什么?

所以,第一,您的实施不正确。您将尝试关闭打开的文件对象,即使它无法打开,这是一个问题。在这种情况下你需要做的是:

@contextmanager
def file_open(path):
    try:
        f_obj = open(path, 'w')
        try:
            yield f_obj
        finally:
            print('Closing file')
            f_obj.close()         
    except OSError:
        print("We had an error!")

或更简单地说:

@contextmanager
def file_open(path):
    try:
        with open(path, 'w') as f_obj:
            yield f_obj
            print('Closing file')
    except OSError:
        print("We had an error!")

到“发电机一般是如何工作的?”我会推荐你​​参考 the existing question 关于那个主题。这个特定的案例很复杂,因为使用 @contextlib.contextmanager 装饰器 将生成器重新用于 一个基本上不相关的目的,使用它们在两种情况下天生暂停的事实:

  1. 创建时(直到请求第一个值)
  2. 在每个 yield 上(当请求每个后续值时)

实施上下文管理。

contextmanager 只是滥用它来制作这样的 class(actual source code 覆盖边缘情况相当复杂):

class contextmanager:
    def __init__(self, gen):
        self.gen = gen  # Receives generator in initial state
    def __enter__(self):
        return next(self.gen)  # Advances to first yield, returning the value it yields
    def __exit__(self, *args):
        if args[0] is not None:
            self.gen.throw(*args)  # Plus some complicated handling to ensure it did the right thing
        else:
            try:
                next(self.gen)      # Check if it yielded more than once
            except StopIteration:
                pass                # Expected to only yield once
            else:
                raise RuntimeError(...)  # Oops, it yielded more than once, that's not supposed to happen

允许生成器的协程元素支持更简单的方法来编写简单的上下文管理器。