Dengan Python, jika saya kembali ke dalam blok "dengan", apakah file akan tetap ditutup?

256

Pertimbangkan yang berikut ini:

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

Apakah file akan ditutup dengan benar, atau apakah menggunakan returnentah bagaimana memotong manajer konteks ?

Lightbreeze
sumber

Jawaban:

238

Ya, itu bertindak seperti finallyblok demi tryblok, yaitu selalu dijalankan (kecuali proses python berakhir dengan cara yang tidak biasa tentu saja).

Ini juga disebutkan dalam salah satu contoh PEP-343 yang merupakan spesifikasi untuk withpernyataan:

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

Namun, sesuatu yang layak disebutkan adalah bahwa Anda tidak dapat dengan mudah menangkap pengecualian yang dilemparkan oleh open()panggilan tanpa menempatkan seluruh withblok di dalam try..exceptblok yang biasanya tidak sesuai dengan yang diinginkan.

Pencuri
sumber
8
elsedapat ditambahkan withuntuk mengatasi try with exceptmasalah itu. sunting: ditambahkan ke bahasa
rplnt
7
Saya tidak tahu apakah itu relevan, tetapi sepengetahuan saya Process.terminate()adalah satu dari sedikit (satu-satunya?) Skenario yang tidak menjamin panggilan finallypernyataan: "Perhatikan bahwa keluar penangan dan akhirnya klausa, dll., Tidak akan dieksekusi. "
Rik Poggi
@RikPoggi os._exitterkadang digunakan - ia keluar dari proses Python tanpa memanggil petugas pembersihan.
Acumenus
2
Mungkin mengejek ular sedikit, tetapi bagaimana jika saya mengembalikan ekspresi generator dari dalam withblok, apakah jaminan berlaku selama generator terus menghasilkan nilai? selama referensi apa saja itu? Yaitu apakah saya perlu menggunakan delatau menetapkan nilai yang berbeda untuk variabel yang menyimpan objek generator?
ack
1
@davidA Setelah file ditutup, referensi masih dapat diakses; Namun, setiap upaya untuk menggunakan referensi untuk tarik / push data ke / dari file akan memberikan: ValueError: I/O operation on closed file..
RWDJ
36

Iya.

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

..adalah setara dengan:

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

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

Lebih tepatnya, __exit__metode dalam manajer konteks selalu dipanggil saat keluar dari blok (terlepas dari pengecualian, pengembalian, dll). Metode objek file __exit__hanya panggilan f.close()(misalnya di sini di CPython )

dbr
sumber
30
Percobaan yang menarik untuk menunjukkan jaminan yang Anda dapatkan dari finallykeywrod adalah: def test(): try: return True; finally: return False.
Ehsan Kia
20

Iya. Secara lebih umum, __exit__metode Manajer Pernyataan Dengan Pernyataan memang akan dipanggil jika terjadi returndari dalam konteks. Ini dapat diuji dengan yang berikut:

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

Outputnya adalah:

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

Output di atas mengkonfirmasi bahwa __exit__itu disebut meskipun awal return. Dengan demikian, manajer konteks tidak dilewati.

Acumenus
sumber
4

Ya, tetapi mungkin ada beberapa efek samping dalam kasus lain, karena mungkin harus melakukan sesuatu (seperti buffer flushing) di __exit__blok

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'
virusdefender
sumber