homepage

This issue tracker has been migrated to GitHub , and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Generator's finally block not run if close() called before first iteration
Type: behavior Stage: resolved
Components: Documentation, Interpreter Core Versions: Python 3.4, Python 2.7
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: docs@python Nosy List: docs@python, martin.panter, mpaolini, pitrou, r.david.murray, sjdrake
Priority: normal Keywords:

Created on 2015年01月13日 07:35 by sjdrake, last changed 2022年04月11日 14:58 by admin. This issue is now closed.

Messages (4)
msg233903 - (view) Author: Stephen Drake (sjdrake) * Date: 2015年01月13日 07:35
If a generator has its close() method called before any items are requested from it, a finally block in the generator function will not be executed.
I encountered this when wrapping an open file to alter the result of iterating over it. Using a generator function with a try/finally block seemed like a simple way of acheiving this. Here's an example that logs each line as it's read:
def logged_lines(f):
 try:
 for line in f:
 logging.warning(line.strip())
 yield line
 finally:
 logging.warning('closing')
 f.close()
If the generator is created and closed immediately, the underlying file-like object is left open:
>>> f = urlopen('https://docs.python.org/')
>>> lines = logged_lines(f)
>>> lines.close()
>>> f.closed
False
But once the first item is requested from the generator, close() will trigger cleanup:
>>> lines = logged_lines(f)
>>> next(lines)
WARNING:root:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n'
>>> lines.close()
WARNING:root:closing
>>> f.closed
True
Having read the documentation for yield expressions, I don't believe this behaviour to be non-conformant - but it still seems like a bit of a gotcha to me. Should this usage be warned against?
msg241794 - (view) Author: Marco Paolini (mpaolini) * Date: 2015年04月22日 10:17
I think there is an issue in the way you designed your cleanup logic. So I think this issue is invalid.
Usually, the code (funcion, class, ...) that *opens* the file should also be resposible of closing it.
option 1) the caller opens and closes the file and wrapping the logged lines in a try/finally
def logged_lines(f):
 try:
 for line in f:
 logging.warning(line.strip())
 yield line
 finally:
 logging.warning('closing')
f = open('yyy', 'r')
try:
 for l in logged_lines(f):
 print(l)
finally:
 f.close()
option 2) the funcion opens and closes the file
def logged_lines(fname):
 f = open('yyy', 'r')
 try:
 for line in f:
 logging.warning(line.strip())
 yield line
 finally:
 logging.warning('closing')
 f.close()
for l in logged_lines('yyy'):
 print(l)
msg241833 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2015年04月22日 23:11
This looks logical to me. The "finally" block is only entered if the "try" block is ever entered, but if you don't consume anything in the generator then the generator's code is never actually executed.
msg241911 - (view) Author: Stephen Drake (sjdrake) * Date: 2015年04月24日 05:17
Ok, I can accept that. I think my mistake was to assume that because a generator has a close() method, I could treat it as a lightweight wrapper for another closeable object.
But it's better to regard a generator function that wraps an iterable as something more akin to map() or filter(), and use a class if it's necessary to wrap a file such that close() is passed through.
I happened to take a fresh look at this just the other day and it also occurred to me that the kind of composition I was trying to do can work if it's generators all the way down:
def open_lines(name, mode='rt', buffering=-1):
 with open(name, mode, buffering) as f:
 for line in f:
 yield line
def logged_lines(f):
 try:
 for line in f:
 logging.warning(line.strip())
 yield line
 finally:
 f.close()
lines = open_lines('yyy', 'r')
if verbose:
 lines = logged_lines(lines)
try:
 for line in lines:
 print(line)
finally:
 lines.close()
So a generator can transparently wrap a plain iterable or another generator, but not closeable objects in general. There's nothing really wrong with that, so I'm happy for this issue to be closed as invalid.
History
Date User Action Args
2022年04月11日 14:58:11adminsetgithub: 67416
2015年05月14日 23:22:23martin.pantersetstatus: open -> closed
resolution: not a bug
stage: resolved
2015年04月24日 05:17:33sjdrakesetmessages: + msg241911
2015年04月22日 23:11:07pitrousetnosy: + pitrou
messages: + msg241833
2015年04月22日 10:17:12mpaolinisetnosy: + mpaolini
messages: + msg241794
2015年04月22日 04:10:12martin.pantersetnosy: + martin.panter
2015年01月13日 13:12:06r.david.murraysetnosy: + r.david.murray
2015年01月13日 07:35:46sjdrakecreate

AltStyle によって変換されたページ (->オリジナル) /