Custom 'with open()' statement in Python: generator didn't yield error

守給你的承諾、 提交于 2020-01-15 12:48:08

问题


I have a class for a file, from which you can parse data, write data etc. I want to use it from any application like this:

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

I tried 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__)

but I got after running first code

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

I guess it's not how contextmanagers works. How to do what I want?


回答1:


Was going to comment but things turn out to be too complicated to leave there, but I do have an answer.

The corrected version of code can be reduced to essentially this

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

Before we try, let's get a count of open file handles using this:

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

Now use our context manager

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

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

Yup, file descriptor count increased, but now that we are out of our context manager, let's see

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

Uh that defeats the purpose of having a context manager for the file (we want to use the default autoclosing function, so restart the interpreter, and try this.

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

Rerun our tests

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

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

Now outside the context manager

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

Yay, looks like it worked (file is successfully closed), but what about a path that does not exist to see that exception handling?

>>> 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

The exception block did fire, note that traceback object, but then since the context manager did not correctly yield, a different exception is raised like what you saw before. I don't know what to recommend, but what I recommend is log the error (using a logger) and then reraise the exception. You could consider returning a dummy object of some kind that will then raise the exception when read, or return nothing, but you need to decide on what exactly is best for your case.




回答2:


Try this:

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

Context managers are generators, not functions.



来源:https://stackoverflow.com/questions/25445177/custom-with-open-statement-in-python-generator-didnt-yield-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!