问题:在Python中,如果我在“ with”块内返回文件,文件是否仍会关闭?
考虑以下:
with open(path, mode) as f:
return [line for line in f if condition]
文件将被正确关闭,还是使用return
某种方式绕过上下文管理器?
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?
回答 0
是的,它的作用就像一个finally
块接一个try
块,也就是说,它总是执行(除非python进程以异常的方式终止)。
PEP-343的示例之一也提到了该with
语句,它是该语句的规范:
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).
但是,值得一提的是,如果open()
不将整个with
块放入try..except
通常不是您想要的块中,就无法轻松捕获调用引发的异常。
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.
回答 1
是。
def example(path, mode):
with open(path, mode) as f:
return [line for line in f if condition]
..几乎等同于:
def example(path, mode):
f = open(path, mode)
try:
return [line for line in f if condition]
finally:
f.close()
更准确地说, __exit__
总是在退出该块时调用上下文管理器中方法(无论异常,返回等如何)。文件对象的__exit__
方法只是调用f.close()
(例如,在CPython中)
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)
回答 2
是。更一般地,如果在上下文内部发生__exit__
了“ with”语句上下文管理器,该方法的确会被调用return
。可以使用以下方法进行测试:
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()
输出为:
Entering context.
Returning inside with-statement.
EXITING context.
上面的输出确认 __exit__
尽管是早期的调用return
。这样,上下文管理器不会被绕过。
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.
回答 3
是的,但是在其他情况下可能会有一些副作用,因为它可能会在__exit__
块中执行某些操作(例如刷新缓冲区)
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'
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'