在Python中自定义'with open()'语句:生成器没有产生错误

时间:2014-08-22 10:44:41

标签: python with-statement

我有一个文件类,你可以从中解析数据,写数据等。我想在任何类似的应用程序中使用它:

f = MyFileClass() # __init__ method puts a lot of default data in object
with f.open() as file: # where f.open() is custom MyFileClass method
    file.write("foo") # file should close automatically after this

我试过了:

# it's in MyFileClass()
from contextlib import contextmanager
@contextmanager
def open(self):
    try:
        return open(os.path.join(self.directory, self.name), self.flags)
    except Exception as e:
        print(e.__traceback__)

但我在运行第一个代码后得到了

line 22, in fill_new_file with f.open() as file: File "C:\Python34\lib\contextlib.py", line 61, in __enter__ raise RuntimeError("generator didn't yield") from None RuntimeError: generator didn't yield

我想这不是上下文管理员的工作方式。怎么做我想要的?

2 个答案:

答案 0 :(得分:7)

试试这个:

@contextmanager
def open(self):
    try:
        yield open(os.path.join(self.directory, self.name), self.flags)
    except Exception as e:
        print(e.__traceback__)

上下文管理器是生成器,而不是函数。

答案 1 :(得分:5)

要发表评论但事情变得太复杂而不能离开那里,但我确实有答案。

更正后的代码版本可以简化为

@contextmanager
def myopen(path):
    try:
        yield open(path)
    except Exception as e:
        print(e.__traceback__)

在我们尝试之前,让我们使用这个来计算打开的文件句柄:

>>> os.listdir('/proc/self/fd')
['0', '1', '2', '3']

现在使用我们的上下文管理器

>>> with myopen('/tmp/a_file') as f:
...     print(f.read())
...     print(os.listdir('/proc/self/fd'))
... 
Contents of file

['0', '1', '2', '3', '4']

是的,文件描述符数量增加了,但是现在我们已经不在我们的上下文管理器中,让我们看看

>>> print(os.listdir('/proc/self/fd'))
['0', '1', '2', '3', '4']

呃违背了为文件设置上下文管理器的目的(我们想使用默认的自动关闭功能,所以重新启动解释器,试试这个。

@contextmanager
def myopen(path):
    try:
        with open(path) as f:
            yield f
    except Exception as e:
        print(e.__traceback__)

重新运行我们的测试

>>> with myopen('/tmp/a_file') as f:
...     print(f.read())
...     print(os.listdir('/proc/self/fd'))
... 
Contents of file

['0', '1', '2', '3', '4']

现在在上下文管理器之外

>>> print(os.listdir('/proc/self/fd'))
['0', '1', '2', '3']

是的,看起来它有效(文件已成功关闭),但是看不到异常处理的路径呢?

>>> with myopen('/tmp/no_path') as f:
...     print(f.read())
... 
<traceback object at 0x7f6b64b618c8>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.4/contextlib.py", line 61, in __enter__
    raise RuntimeError("generator didn't yield") from None
RuntimeError: generator didn't yield

异常块确实触发了,注意traceback对象,但是由于上下文管理器没有正确屈服,所以会像你之前看到的那样引发一个不同的异常。我不知道该推荐什么,但我建议记录错误(使用记录器),然后重新加载异常。您可以考虑返回某种虚拟对象,然后在读取时引发异常,或者不返回任何内容,但您需要确定最适合您案例的内容。