Bagaimana cara membaca file baris demi baris dengan Python?

138

Di zaman prasejarah (Python 1.4) kami melakukan:

fp = open('filename.txt')
while 1:
    line = fp.readline()
    if not line:
        break
    print line

setelah Python 2.1, kami melakukan:

for line in open('filename.txt').xreadlines():
    print line

sebelum kami mendapatkan protokol iterator yang nyaman di Python 2.3, dan dapat melakukan:

for line in open('filename.txt'):
    print line

Saya telah melihat beberapa contoh menggunakan lebih banyak verbose:

with open('filename.txt') as fp:
    for line in fp:
        print line

apakah ini metode yang disukai untuk maju?

[Sunting] Saya mengerti bahwa pernyataan with memastikan penutupan file ... tetapi mengapa itu tidak termasuk dalam protokol iterator untuk objek file?

thebjorn
sumber
4
imho, saran terakhir tidak lebih bertele-tele dari yang sebelumnya. Itu hanya melakukan lebih banyak pekerjaan (memastikan file ditutup saat Anda selesai).
azhrei
1
@azhrei itu satu baris lagi, jadi obyektif itu lebih bertele-tele.
thebjorn
7
Saya mengerti apa yang Anda katakan tetapi saya hanya mengatakan membandingkan apel dengan apel, saran terakhir kedua di posting Anda membutuhkan kode penanganan pengecualian juga agar sesuai dengan apa yang dilakukan opsi terakhir. Jadi dalam praktiknya, ini lebih bertele-tele. Saya kira itu tergantung pada konteks mana dari dua opsi terakhir yang terbaik, sungguh.
azhrei

Jawaban:

232

Tepat ada satu alasan mengapa hal berikut lebih disukai:

with open('filename.txt') as fp:
    for line in fp:
        print line

Kita semua dimanjakan oleh skema penghitungan referensi yang relatif deterministik dari CPython untuk pengumpulan sampah. Implementasi hipotetis lain dari Python tidak akan selalu menutup file "cukup cepat" tanpa withblok jika mereka menggunakan skema lain untuk merebut kembali memori.

Dalam implementasi seperti itu, Anda mungkin mendapatkan kesalahan "terlalu banyak file yang dibuka" dari OS jika kode Anda membuka file lebih cepat daripada pemanggil sampah yang memanggil finalizer pada pegangan file yatim piatu. Solusi yang biasa dilakukan adalah dengan segera memicu GC, tetapi ini adalah peretasan yang buruk dan harus dilakukan oleh setiap fungsi yang dapat mengalami kesalahan, termasuk yang ada di perpustakaan. Sungguh mimpi buruk.

Atau Anda bisa menggunakan withblok saja.

Pertanyaan Bonus

(Hentikan membaca sekarang jika hanya tertarik pada aspek obyektif dari pertanyaan tersebut.)

Mengapa itu tidak termasuk dalam protokol iterator untuk objek file?

Ini adalah pertanyaan subjektif tentang desain API, jadi saya memiliki jawaban subjektif dalam dua bagian.

Pada dasarnya, hal ini terasa salah, karena membuat protokol iterator melakukan dua hal terpisah — mengulang baris dan menutup pegangan file — dan sering kali merupakan ide yang buruk untuk membuat fungsi yang tampak sederhana melakukan dua tindakan. Dalam hal ini, rasanya sangat buruk karena iterator berhubungan dengan cara kuasi-fungsional, berbasis nilai dengan konten file, tetapi mengelola pegangan file adalah tugas yang sepenuhnya terpisah. Menggabungkan keduanya, tanpa terlihat, menjadi satu tindakan, mengejutkan manusia yang membaca kode dan membuatnya lebih sulit untuk bernalar tentang perilaku program.

Bahasa lain pada dasarnya sampai pada kesimpulan yang sama. Haskell secara singkat tergoda dengan apa yang disebut "lazy IO" yang memungkinkan Anda untuk mengulang file dan menutupnya secara otomatis ketika Anda sampai ke akhir streaming, tetapi hampir secara universal tidak disarankan untuk menggunakan lazy IO di Haskell hari ini, dan Haskell sebagian besar pengguna telah pindah ke manajemen sumber daya yang lebih eksplisit seperti Conduit yang berperilaku lebih seperti withblok di Python.

Pada tingkat teknis, ada beberapa hal yang mungkin ingin Anda lakukan dengan pegangan file dengan Python yang tidak akan berfungsi dengan baik jika iterasi menutup pegangan file. Misalnya, saya perlu mengulang file dua kali:

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

Meskipun ini adalah kasus penggunaan yang kurang umum, pertimbangkan fakta bahwa saya mungkin baru saja menambahkan tiga baris kode di bagian bawah ke basis kode yang ada yang awalnya memiliki tiga baris teratas. Jika iterasi menutup file, saya tidak akan bisa melakukan itu. Jadi, menjaga iterasi dan manajemen sumber daya tetap terpisah membuatnya lebih mudah untuk menyusun potongan kode menjadi program Python yang lebih besar dan berfungsi.

Komposabilitas adalah salah satu fitur kegunaan terpenting dari suatu bahasa atau API.

Dietrich Epp
sumber
1
+1 karena menjelaskan "ketika" dalam komentar saya di op ;-)
azhrei
bahkan dengan implementasi alternatif, with handler hanya akan menyebabkan masalah bagi Anda untuk program yang membuka ratusan file secara berurutan. Sebagian besar program dapat bertahan dengan referensi file yang menjuntai tanpa masalah. Kecuali Anda menonaktifkannya, pada akhirnya GC akan bekerja dan membersihkan file handle. withmemang memberi Anda ketenangan pikiran, jadi ini masih merupakan praktik terbaik.
Lie Ryan
1
@DietrichEpp: mungkin "referensi file yang menjuntai" bukanlah kata yang tepat, maksud saya sebenarnya menangani file yang tidak lagi dapat diakses tetapi belum ditutup. Bagaimanapun, GC akan menutup tuas file ketika mengumpulkan objek file, oleh karena itu selama Anda tidak memiliki referensi tambahan ke objek file dan Anda tidak menonaktifkan GC dan Anda tidak membuka banyak file dengan cepat. berturut-turut, Anda tidak mungkin mendapatkan "terlalu banyak file terbuka" karena tidak menutup file.
Lie Ryan
1
Ya, itulah yang saya maksud dengan "jika kode Anda membuka file lebih cepat daripada pengumpul sampah panggilan finalisator pada pegangan file yatim piatu".
Dietrich Epp
1
Alasan yang lebih besar untuk digunakan dengan adalah bahwa jika Anda tidak menutup file, itu tidak akan langsung ditulis.
Antimony
21

Iya,

with open('filename.txt') as fp:
    for line in fp:
        print line

adalah cara untuk pergi.

Ini tidak lebih bertele-tele. Itu lebih aman.

eumiro
sumber
5

jika Anda dimatikan oleh baris tambahan, Anda dapat menggunakan fungsi pembungkus seperti ini:

def with_iter(iterable):
    with iterable as iter:
        for item in iter:
            yield item

for line in with_iter(open('...')):
    ...

di Python 3.3, yield frompernyataan itu akan membuat ini lebih pendek:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
Lie Ryan
sumber
2
panggil fungsi xreadlines .. dan letakkan di file bernama xreadlines.py dan kita kembali ke sintaks Python 2.1 :-)
thebjorn
@thebjorn: mungkin, tetapi contoh Python 2.1 yang Anda kutip tidak aman dari penangan file yang tidak ditutup dalam implementasi alternatif. Pembacaan file Python 2.1 yang aman dari file handler yang tidak ditutup akan membutuhkan setidaknya 5 baris.
Lie Ryan
-1
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
Rekaut
sumber
5
Ini tidak benar-benar menjawab pertanyaan
Thayne