Bagaimana saya bisa membuka banyak file menggunakan "dengan terbuka" dengan Python?

672

Saya ingin mengubah beberapa file sekaligus, jika saya bisa menulis kepada mereka semua. Saya ingin tahu apakah saya dapat menggabungkan beberapa panggilan terbuka dengan withpernyataan:

try:
  with open('a', 'w') as a and open('b', 'w') as b:
    do_something()
except IOError as e:
  print 'Operation failed: %s' % e.strerror

Jika itu tidak mungkin, seperti apa solusi yang elegan untuk masalah ini?

Frantischeck003
sumber

Jawaban:

1052

Pada Python 2.7 (atau 3.1 masing-masing) Anda dapat menulis

with open('a', 'w') as a, open('b', 'w') as b:
    do_something()

Di versi Python yang lebih lama, Anda kadang-kadang bisa menggunakan contextlib.nested()manajer konteks sarang. Ini tidak akan berfungsi seperti yang diharapkan untuk membuka file banyak, - lihat dokumentasi tertaut untuk detailnya.


Dalam kasus yang jarang terjadi, Anda ingin membuka sejumlah file variabel secara bersamaan, Anda dapat menggunakan contextlib.ExitStack, mulai dari Python versi 3.3:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Do something with "files"

Namun, sebagian besar waktu Anda memiliki kumpulan file variabel, Anda mungkin ingin membukanya satu per satu.

Sven Marnach
sumber
5
Sayangnya, menurut dokumen contextlib.nested, Anda tidak boleh menggunakannya untuk membuka file: "using nested () untuk membuka dua file adalah kesalahan pemrograman karena file pertama tidak akan segera ditutup jika pengecualian dilemparkan saat membuka file file kedua. "
weronika
41
apakah ada cara yang digunakan withuntuk membuka daftar file variabel?
monkut
23
@monkut: Pertanyaan yang sangat bagus (Anda sebenarnya bisa menanyakan ini sebagai pertanyaan terpisah). Jawaban singkat: Ya, ada ExitStackpada Python 3.3. Tidak ada cara mudah untuk melakukan ini di versi Python sebelumnya.
Sven Marnach
12
Apakah mungkin untuk memiliki sintaks span beberapa baris ini?
tommy.carstensen
9
@ tommy.carstensen: Anda dapat menggunakan mekanisme kelanjutan garis yang biasa . Anda mungkin harus menggunakan garis backslash untuk memecah koma, seperti yang direkomendasikan oleh PEP 9 .
Sven Marnach
99

Cukup ganti anddengan ,dan Anda selesai:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror
Michael
sumber
3
Anda harus menentukan versi Python mana yang mendukung sintaks ini.
Craig McQueen
58

Untuk membuka banyak file sekaligus atau untuk jalur file yang panjang, mungkin berguna untuk memecah banyak hal menjadi beberapa baris. Dari Panduan Gaya Python seperti yang disarankan oleh @Sven Marnach dalam komentar ke jawaban lain:

with open('/path/to/InFile.ext', 'r') as file_1, \
     open('/path/to/OutFile.ext', 'w') as file_2:
    file_2.write(file_1.read())
Michael Ohlrogge
sumber
1
Dengan indentasi ini saya mendapatkan: "flake8: garis kelanjutan terlalu-indentasi untuk indentasi visual"
Louis M
@LouisM Kedengarannya seperti sesuatu yang berasal dari editor atau lingkungan Anda, bukan dari python dasar. Jika terus menjadi masalah bagi Anda, saya akan merekomendasikan membuat pertanyaan baru yang berkaitan dengannya dan memberikan lebih banyak detail pada editor dan lingkungan Anda.
Michael Ohlrogge
3
Ya itu pasti editor saya, dan itu hanya peringatan. Yang ingin saya tekankan adalah bahwa lekukan Anda tidak sesuai dengan PEP8. Anda harus memberi indentasi pada open kedua () dengan 8 spasi alih-alih sejajar dengan yang pertama.
Louis M
2
@LouisM PEP8 adalah pedoman , bukan aturan, dan dalam hal ini saya pasti akan mengabaikannya
Nick
2
Ya, tidak ada masalah dengan itu, mungkin berguna bagi orang lain dengan linter otomatis :)
Louis M
15

Bersarang dengan pernyataan akan melakukan pekerjaan yang sama, dan menurut saya, lebih mudah untuk dihadapi.

Katakanlah Anda memiliki inFile.txt, dan ingin menulisnya menjadi dua outFile secara bersamaan.

with open("inFile.txt", 'r') as fr:
    with open("outFile1.txt", 'w') as fw1:
        with open("outFile2.txt", 'w') as fw2:
            for line in fr.readlines():
                fw1.writelines(line)
                fw2.writelines(line)

EDIT:

Saya tidak mengerti alasan downvote. Saya menguji kode saya sebelum menerbitkan jawaban saya, dan itu berfungsi seperti yang diinginkan: Itu menulis ke semua outFile, seperti pertanyaannya. Tidak ada duplikat tulisan atau gagal menulis. Jadi saya benar-benar ingin tahu mengapa jawaban saya dianggap salah, suboptimal atau semacamnya.

FatihAkici
sumber
1
Saya tidak tahu apa yang orang lain downvoted Anda, tetapi saya UPVOTED Anda karena ini adalah satu-satunya contoh yang memiliki tiga file (satu input, dua output) yang kebetulan hanya apa yang saya butuhkan.
Adam Michael Wood
2
mungkin Anda downvoted bcoz di Python> 2.6 Anda dapat menulis lebih banyak kode pythonic - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (mengapa saya tidak dapat memasukkan fragmen kode dalam komentar ?!) Kami berada di 2018;) jadi versi kuno di masa lalu
El Ruso
2
Pengingat yang ramah untuk python pooo-poohing 2.6: CentOS 6 (yang tidak EOL sampai November 2020), masih menggunakan py2.6 secara default. Jadi jawaban ini (sampai sekarang) masih merupakan IMO terbaik secara keseluruhan.
BJ Black
11

Sejak Python 3.3, Anda dapat menggunakan kelas ExitStackdari contextlibmodul untuk
membuka sejumlah file secara aman .

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 .

Bahkan, 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

Jika Anda tertarik dengan detailnya, berikut adalah contoh umum untuk menjelaskan cara ExitStackberoperasi:

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(len(stack._exit_callbacks)) # number of callbacks called on exit
    nums = [stack.enter_context(x) for x in xs]
    print(len(stack._exit_callbacks))

print(len(stack._exit_callbacks))
print(nums)

Keluaran:

0
enter X1
enter X2
enter X3
3
exit X3
exit X2
exit X1
0
[1, 2, 3]
timgeb
sumber
3

Dengan python 2.6 Ini tidak akan berfungsi, kita harus menggunakan cara di bawah ini untuk membuka banyak file:

with open('a', 'w') as a:
    with open('b', 'w') as b:
Aashutosh jha
sumber
1

Jawaban telat (8 tahun), tetapi bagi seseorang yang ingin menggabungkan beberapa file menjadi satu , fungsi berikut mungkin bisa membantu:

def multi_open(_list):
    out=""
    for x in _list:
        try:
            with open(x) as f:
                out+=f.read()
        except:
            pass
            # print(f"Cannot open file {x}")
    return(out)

fl = ["C:/bdlog.txt", "C:/Jts/tws.vmoptions", "C:/not.exist"]
print(multi_open(fl))

2018-10-23 19:18:11.361 PROFILE  [Stop Drivers] [1ms]
2018-10-23 19:18:11.361 PROFILE  [Parental uninit] [0ms]
...
# This file contains VM parameters for Trader Workstation.
# Each parameter should be defined in a separate line and the
...
HUBUNGI19
sumber