ChatGPT解决这个技术问题 Extra ChatGPT

In Python, if I return inside a "with" block, will the file still close?

Consider the following:

with open(path, mode) as f:
    return [line for line in f if condition]

Will the file be closed properly, or does using return somehow bypass the context manager?


T
ThiefMaster

Yes, it acts like the finally block after a try block, i.e. it always executes (unless the python process terminates in an unusual way of course).

It is also mentioned in one of the examples of PEP-343 which is the specification for the with statement:

with locked(myLock):
    # Code here executes with myLock held.  The lock is
    # guaranteed to be released when the block is left (even
    # if via return or by an uncaught exception).

Something worth mentioning is however, that you cannot easily catch exceptions thrown by the open() call without putting the whole with block inside a try..except block which is usually not what one wants.


else could be added to with to solve that try with except problem. edit: added to language
I don't know if it's relevant, but to my knowledge Process.terminate() is one of the few (the only?) scenario that doesn't guarantee the call of a finally statement: "Note that exit handlers and finally clauses, etc., will not be executed."
@RikPoggi os._exit is sometimes used - it exits the Python process without calling cleanup handlers.
Perhaps taunting the snake a bit, but what if I return a generator expression from within the with block, does the guarantee hold for as long as the generator keeps yielding values? for as long as anything references it? I.e. do i need to use del or assign a different value to the variable which holds the generator object?
@davidA After the file is closed, the references are still accessible; however, any attempts to use the references to pull/push data to/from the file will give: ValueError: I/O operation on closed file..
d
dbr

Yes.

def example(path, mode):
    with open(path, mode) as f:
        return [line for line in f if condition]

..is pretty much equivalent to:

def example(path, mode):
    f = open(path, mode)

    try:
        return [line for line in f if condition]
    finally:
        f.close()

More accurately, the __exit__ method in a context manager is always called when exiting the block (regardless of exceptions, returns etc). The file object's __exit__ method just calls f.close() (e.g here in CPython)


An interesting experiment to show the guarantee you get from the finally keywrod is: def test(): try: return True; finally: return False.
A
Asclepius

Yes. More generally, the __exit__ method of a With Statement Context Manager will indeed be called in the event of a return from inside the context. This can be tested with the following:

class MyResource:
    def __enter__(self):
        print('Entering context.')
        return self

    def __exit__(self, *exc):
        print('EXITING context.')

def fun():
    with MyResource():
        print('Returning inside with-statement.')
        return
    print('Returning outside with-statement.')

fun()

The output is:

Entering context.
Returning inside with-statement.
EXITING context.

The output above confirms that __exit__ was called despite the early return. As such, the context manager is not bypassed.


v
virusdefender

Yes, but there may be some side effect in other cases, because it may should do something (like flushing buffer) in __exit__ block

import gzip
import io

def test(data):
    out = io.BytesIO()
    with gzip.GzipFile(fileobj=out, mode="wb") as f:
        f.write(data)
        return out.getvalue()

def test1(data):
    out = io.BytesIO()
    with gzip.GzipFile(fileobj=out, mode="wb") as f:
        f.write(data)
    return out.getvalue()

print(test(b"test"), test1(b"test"))

# b'\x1f\x8b\x08\x00\x95\x1b\xb3[\x02\xff' b'\x1f\x8b\x08\x00\x95\x1b\xb3[\x02\xff+I-.\x01\x00\x0c~\x7f\xd8\x04\x00\x00\x00'

In function test, out.getvalue() happens before __exit__ is executed, so the result is as expected.