У Python, якщо я повернусь всередину блоку "з", файл все ще закриється?


Відповіді:


238

Так, він діє як 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блоку, який зазвичай не є тим, чого хочеться.


8
elseможна було б додати, withщоб вирішити цю try with exceptпроблему. редагувати: додано до мови
rplnt

7
Я не знаю, чи це актуально, але, наскільки мені відомо, Process.terminate()це один з небагатьох (єдиних?) Сценаріїв, який не гарантує виклик finallyзаяви: "Зауважте, що обробники виходу та нарешті пункти тощо не будуть страчений ».
Рік Поггі

os._exitІноді використовується @RikPoggi - він закриває процес Python, не викликаючи оброблювачів очищення.
Акумен

2
Можливо, трохи глузує змію, але що робити, якщо я поверну вираз генератора зсередини withблоку, чи зберігається гарантія, поки генератор зберігає врожайні значення? доки що-небудь посилається на це? Тобто мені потрібно використовувати delабо призначити інше значення змінній, яка містить об’єкт генератора?
ак

1
@davidA Після закриття файлу посилання залишаються доступними; Однак будь-які спроби використовувати посилання на тягнути / PUSH даних в / з файлу дасть: ValueError: I/O operation on closed file..
RWDJ

36

Так.

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 )


30
Цікавий експеримент , щоб показати гарантії ви отримуєте від finallykeywrod є: def test(): try: return True; finally: return False.
Ehsan Kia

20

Так. Більш загально, __exit__метод менеджера контексту із заявою дійсно буде викликаний у випадку 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. Таким чином, менеджер контексту не обходить стороною.


4

Так, але в інших випадках може бути якийсь побічний ефект, оскільки він може щось робити (наприклад, промивний буфер) в __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'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.