Beberapa variabel dalam pernyataan 'dengan'?

391

Apakah mungkin untuk mendeklarasikan lebih dari satu variabel menggunakan withpernyataan dengan Python?

Sesuatu seperti:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

... atau apakah membersihkan dua sumber daya sekaligus masalah?

ikan buntal
sumber
Mungkin seperti ini: dengan [expr1, expr2] sebagai f: dan kemudian gunakan f [0] dan f [1].
jbasko
Akan menyenangkan karena tidak perlu mengimpor sesuatu .... tetapi tidak berhasil AttributeError: objek 'daftar' tidak memiliki atribut ' keluar '
pufferfish
Jika python baru saja ditutup, Anda tidak perlu pernyataan with
BT
Anda tidak perlu menggunakan pernyataan with, kan? Anda bisa mengatur file_out dan file_in ke None, lalu coba / kecuali / akhirnya di mana Anda membuka dan memprosesnya di coba, dan kemudian di akhirnya menutupnya jika tidak ada. Tidak perlu indentasi ganda untuk itu.
M Katz
1
Banyak dari jawaban ini tidak berurusan dengan kebutuhan akan lebih dari dua dengan pernyataan. Secara teoritis mungkin ada aplikasi yang perlu membuka puluhan konteks, yang bersarang berantakan sangat cepat adalah setiap batasan panjang garis dikenakan.
ThorSummoner

Jawaban:

667

Dimungkinkan dalam Python 3 sejak v3.1 dan Python 2.7 . withSintaks baru mendukung beberapa manajer konteks:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

Berbeda dengan contextlib.nested, ini menjamin itu adan bakan memiliki panggilan mereka __exit__()bahkan jika C()atau __enter__()metode itu menimbulkan pengecualian.

Anda juga dapat menggunakan variabel sebelumnya dalam definisi selanjutnya (h / t Ahmad di bawah):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)
Rafał Dowgird
sumber
1
apakah mungkin mengatur bidang sama dengan sesuatu dengan pernyataan seperti pada with open('./file') as arg.x = file:?
Charlie Parker
13
Juga, dimungkinkan: dengan A () sebagai a, B (a) sebagai b, C (a, b) sebagai c:
Ahmad Yoosofan
kelas test2: x = 1; t2 = test2 () dengan open ('f2.txt') sebagai t2.x: untuk l1 di t2.x.readlines (): print (l1); # Charlie Parker # diuji dengan python 3.6
Ahmad Yoosofan
1
harap dicatat, asbersifat opsional.
Sławomir Lenart
untuk memperjelas apa yang dikatakan @ SławomirLenart: asdiperlukan jika Anda memerlukan objek aatau b, tetapi keseluruhan as aatau as btidak diperlukan
Ciprian Tomoiagă
56

contextlib.nested mendukung ini:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

Pembaruan:
Mengutip dokumentasi, mengenai contextlib.nested:

Sudah tidak digunakan lagi sejak versi 2.7 : Pernyataan with sekarang mendukung fungsi ini secara langsung (tanpa quirks rawan kesalahan yang membingungkan).

Lihat jawaban Rafał Dowgird untuk informasi lebih lanjut.

Alex Martelli
sumber
34
Saya minta maaf untuk mengatakan itu, tetapi saya berpikir bahwa nestedmanajer konteks adalah kesalahan dan tidak boleh digunakan. Dalam contoh ini, jika membuka file kedua menimbulkan pengecualian, file pertama tidak akan ditutup sama sekali, sehingga benar-benar menghancurkan tujuan menggunakan manajer konteks.
Rafał Dowgird
Mengapa kamu mengatakannya? Dokumentasi mengatakan bahwa menggunakan nested setara dengan nested 'with's
James Hopkin
@ Rafal: pandangan sekilas ke petunjuk tampaknya mengindikasikan bahwa python menyatu dengan pernyataan dengan benar. Masalah sebenarnya adalah jika file kedua melempar pengecualian pada saat ditutup.
Tidak diketahui
10
@James: Tidak, kode yang setara dalam dokumen di docs.python.org/library/contextlib.html#contextlib.nested berbeda dari withblok bersarang standar . Manajer dibuat secara berurutan sebelum memasukkan dengan: m1, m2, m3 = A (), B (), C () Jika B () atau C () gagal dengan pengecualian, maka satu-satunya harapan Anda untuk menyelesaikan A dengan benar ( ) adalah pemulung.
Rafał Dowgird
8
Sudah usang sejak versi 2.7 . Catatan: Pernyataan with sekarang mendukung fungsi ini secara langsung (tanpa quirks rawan kesalahan yang membingungkan).
miku
36

Perhatikan bahwa jika Anda membagi variabel menjadi beberapa baris, Anda harus menggunakan garis miring terbalik untuk membungkus baris baru.

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

Kurung tidak berfungsi, karena Python malah membuat tuple.

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

Karena tupel tidak memiliki __enter__atribut, Anda mendapatkan kesalahan (tidak deskriptif dan tidak mengidentifikasi tipe kelas):

AttributeError: __enter__

Jika Anda mencoba menggunakan asdalam tanda kurung, Python menangkap kesalahan pada saat parse:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

SyntaxError: sintaks tidak valid

https://bugs.python.org/issue12782 tampaknya terkait dengan masalah ini.

nyanpasu64
sumber
16

Saya pikir Anda ingin melakukan ini sebagai gantinya:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)
Andrew Hare
sumber
5
Begitulah cara saya saat ini melakukannya, tetapi kemudian sarangnya dua kali lebih dalam dari yang saya inginkan (berarti) ...
pufferfish
Saya pikir ini adalah pendekatan terbersih - pendekatan lain akan lebih sulit dibaca. Jawaban Alex Martelli tampaknya lebih dekat dengan apa yang Anda inginkan tetapi jauh lebih mudah dibaca. Mengapa bersarang begitu memprihatinkan?
Andrew Hare
7
Memang bukan masalah besar, tapi, per "impor ini" (alias "Zen Python"), "flat lebih baik daripada bersarang" - itu sebabnya kami menambahkan contextlib.nested ke perpustakaan standar. BTW, 3.1 mungkin memiliki sintaks baru "dengan A () sebagai a, B () sebagai b:" (tambalan ada, sejauh ini tidak ada pernyataan BDFL) untuk dukungan yang lebih langsung (jadi jelas solusi perpustakaan bukan T dianggap sempurna ... tetapi menghindari bersarang yang tidak diinginkan jelas merupakan tujuan bersama di antara pengembang inti Python).
Alex Martelli
2
@Alex: Sangat benar tetapi kita juga harus mempertimbangkan bahwa "Keterbacaan penting".
Andrew Hare
4
@Andrew: Saya pikir satu tingkat lekukan lebih baik mengekspresikan logika yang dimaksud dari program, yaitu untuk "secara atomik" membuat dua variabel, dan membersihkannya kemudian bersama-sama (saya menyadari ini bukan yang sebenarnya terjadi). Pikirkan masalah pengecualian adalah pemecah kesepakatan sekalipun
buntal
12

Sejak Python 3.3, Anda dapat menggunakan kelas ExitStackdari contextlibmodul.

Itu dapat mengelola sejumlah objek sadar konteks yang dinamis , yang berarti bahwa itu akan terbukti sangat berguna jika Anda tidak tahu berapa banyak file yang akan Anda tangani.

Kasus penggunaan kanonik yang disebutkan dalam dokumentasi adalah mengelola sejumlah file dinamis.

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

Ini adalah contoh umum:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

Keluaran:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]
timgeb
sumber
0

Dalam Python 3.1+ Anda dapat menentukan beberapa ekspresi konteks, dan mereka akan diproses seolah-olah banyak withpernyataan bersarang:

with A() as a, B() as b:
    suite

setara dengan

with A() as a:
    with B() as b:
        suite

Ini juga berarti Anda dapat menggunakan alias dari ekspresi pertama di kedua (berguna ketika bekerja dengan koneksi db / kursor):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Eugene Yarmash
sumber