Bagaimana saya bisa membaca file teks besar dengan Python, baris demi baris, tanpa memuatnya ke dalam memori?

239

Saya perlu membaca file besar, baris demi baris. Katakanlah file tersebut memiliki lebih dari 5GB dan saya perlu membaca setiap baris, tetapi jelas saya tidak ingin menggunakannya readlines()karena akan membuat daftar yang sangat besar dalam memori.

Bagaimana kode di bawah ini berfungsi untuk kasus ini? Apakah dengan xreadlinessendirinya membaca satu per satu ke dalam memori? Apakah ekspresi generator diperlukan?

f = (line for line in open("log.txt").xreadlines())  # how much is loaded in memory?

f.next()  

Plus, apa yang bisa saya lakukan untuk membaca ini dalam urutan terbalik, seperti tailperintah Linux ?

Saya menemukan:

http://code.google.com/p/pytailer/

dan

" kepala python, ekor dan terbelakang dibaca oleh baris file teks "

Keduanya bekerja dengan sangat baik!

Bruno Rocha - rochacbruno
sumber
Dan apa yang bisa saya lakukan untuk membaca ini dari ekor? baris demi baris, mulai dari baris terakhir.
Bruno Rocha - rochacbruno
ini harus menjadi pertanyaan terpisah
cmcginty

Jawaban:

311

Saya memberikan jawaban ini karena Keith's, meskipun ringkas, tidak menutup file secara eksplisit

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
John La Rooy
sumber
32
pertanyaannya adalah, "untuk saluran infile" akan memuat 5GB baris saya ke dalam memori? dan, Bagaimana saya bisa membaca dari ekor?
Bruno Rocha - rochacbruno
68
@rochacbruno, itu hanya membaca satu baris pada satu waktu. Ketika baris berikutnya dibaca, yang sebelumnya akan menjadi sampah yang dikumpulkan kecuali jika Anda telah menyimpan referensi ke tempat lain
John La Rooy
1
@rochacbruno, Membaca garis dalam urutan terbalik tidak mudah dilakukan secara efisien sayangnya. Secara umum Anda ingin membaca dari akhir file dalam potongan berukuran masuk akal (kilobyte ke megabyte katakan) dan membaginya pada karakter baris baru (atau apa pun karakter akhir baris di platform Anda)
John La Rooy
4
Terima kasih! Saya menemukan solusi ekor stackoverflow.com/questions/5896079/...
Bruno Rocha - rochacbruno
1
@ Bawejakunal, Maksud Anda jika sebuah baris terlalu panjang untuk dimuat ke memori sekaligus? Itu tidak biasa untuk file teks . Alih-alih menggunakan forloop yang berulang di atas garis, Anda dapat menggunakan chunk = infile.read(chunksize)untuk membaca potongan ukuran terbatas terlepas dari kontennya. Anda harus mencari sendiri baris baru di dalam chunks.
John La Rooy
60

Yang perlu Anda lakukan adalah menggunakan objek file sebagai iterator.

for line in open("log.txt"):
    do_something_with(line)

Lebih baik lagi menggunakan manajer konteks dalam versi Python terbaru.

with open("log.txt") as fileobject:
    for line in fileobject:
        do_something_with(line)

Ini juga akan secara otomatis menutup file.

Keith
sumber
2
Itu tidak memuat seluruh file ke dalam memori?
Bruno Rocha - rochacbruno
17

Pendekatan sekolah lama:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
sumber
2
komentar minor: untuk keselamatan pengecualian disarankan untuk menggunakan pernyataan 'with', dalam kasus Anda "dengan open (nama file, 'rt') sebagai fh:"
prokher
16
@prokher: Ya, tapi saya menyebut ini "sekolah tua".
PTBNL
15

Anda lebih baik menggunakan iterator saja. Relevan: http://docs.python.org/library/fileinput.html

Dari dokumen:

import fileinput
for line in fileinput.input("filename"):
    process(line)

Ini akan menghindari menyalin seluruh file ke memori sekaligus.

Mikola
sumber
Meskipun docs memperlihatkan snippet sebagai "tipikal menggunakan", menggunakannya tidak memanggil close()metode FileInputobjek kelas yang dikembalikan ketika loop selesai - jadi saya akan menghindari menggunakannya dengan cara ini. Dalam Python 3.2 mereka akhirnya telah membuat fileinputkompatibel dengan protokol manajer konteks yang membahas masalah ini (tetapi kode masih tidak akan ditulis seperti yang ditunjukkan).
martineau
7

Inilah yang Anda lakukan jika Anda tidak memiliki baris baru dalam file:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ariel Cabib
sumber
Meskipun saya menyukai metode ini, Anda berisiko mengambil garis dalam teks Anda yang dipecah menjadi beberapa bagian. Saya melihat ini secara pribadi, yang berarti bahwa jika Anda mencari sstring di file seperti saya, saya akan kehilangan beberapa karena mereka berada di garis dipecah menjadi potongan-potongan. Apakah ada cara untuk mengatasi ini? Menggunakan readlines tidak berfungsi dengan baik karena saya mendapatkan salah hitung @Ariel Cabib
edo101
6

Silakan coba ini:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
jyoti das
sumber
tolong jelaskan?
Nikhil VJ
3
Dari dokumen resmi Python: tautan Argumen buffering opsional menentukan ukuran buffer yang diinginkan file: 0 berarti tidak dibuat buffer, 1 berarti buffer baris, nilai positif lainnya berarti menggunakan buffer dengan (kira-kira) ukuran itu (dalam byte). Buffer negatif berarti menggunakan default sistem, yang biasanya buffer baris untuk perangkat tty dan buffering penuh untuk file lain. Jika dihilangkan, standar sistem digunakan
jyoti das
Menyelamatkan hari saya, dalam kasus saya, dengan> ~ file 4gb dengan dua file handler (satu baca, yang lain tulis) python sedang hang dan sekarang baik-baik saja! Terima kasih.
Xelt
@ jyotidas Walaupun saya suka metode ini, Anda berisiko mengambil garis dalam teks yang dipecah menjadi beberapa bagian. Saya melihat ini secara pribadi, yang berarti bahwa jika Anda mencari sstring di file seperti saya, saya akan kehilangan beberapa karena mereka berada di garis dipecah menjadi potongan-potongan. Apakah ada cara untuk mengatasi ini? Menggunakan readlines tidak berfungsi dengan baik karena saya mendapat salah hitung
edo101
3

Aku tidak percaya itu bisa semudah jawaban @ john-la-rooy membuatnya tampak. Jadi, saya membuat ulang cpperintah menggunakan baris demi baris membaca dan menulis. CEPAT GILA.

#!/usr/bin/env python3.6

import sys

with open(sys.argv[2], 'w') as outfile:
    with open(sys.argv[1]) as infile:
        for line in infile:
            outfile.write(line)
Bruno Bronosky
sumber
CATATAN: Karena readlineujung jalur standar python , ini memiliki efek samping mengubah dokumen dengan ujung jalur DOS \r\nke ujung garis Unix \n. Seluruh alasan saya untuk mencari topik ini adalah bahwa saya perlu mengonversi file log yang menerima tumpukan akhiran garis (karena pengembang secara membabi buta menggunakan berbagai pustaka .NET). Saya terkejut menemukan bahwa setelah tes kecepatan awal saya, saya tidak perlu kembali dan rstripgaris. Itu sudah sempurna!
Bruno Bronosky
2

Proyek kebakaran telah berlangsung jauh selama 6 tahun terakhir. Ini memiliki API sederhana yang mencakup subset fitur panda yang berguna.

dask.dataframe menangani chunking secara internal, mendukung banyak operasi yang paralel dan memungkinkan Anda untuk mengekspor irisan kembali ke panda dengan mudah untuk operasi dalam memori.

import dask.dataframe as dd

df = dd.read_csv('filename.csv')
df.head(10)  # return first 10 rows
df.tail(10)  # return last 10 rows

# iterate rows
for idx, row in df.iterrows():
    ...

# group by my_field and return mean
df.groupby(df.my_field).value.mean().compute()

# slice by column
df[df.my_field=='XYZ'].compute()
jpp
sumber
2

Inilah kode untuk memuat file teks dari berbagai ukuran tanpa menyebabkan masalah memori. Ini mendukung file berukuran gigabytes

https://gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

unduh file data_loading_utils.py dan impor ke dalam kode Anda

pemakaian

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(data, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=self.process_lines)

Metode process_lines adalah fungsi callback. Itu akan dipanggil untuk semua baris, dengan data parameter mewakili satu baris file pada satu waktu.

Anda dapat mengonfigurasi variabel CHUNK_SIZE tergantung pada konfigurasi perangkat keras mesin Anda.

Iyvin Jose
sumber
Meskipun saya menyukai metode ini, Anda berisiko mengambil garis dalam teks Anda yang dipecah menjadi beberapa bagian. Saya melihat ini secara pribadi, yang berarti bahwa jika Anda mencari sstring di file seperti saya, saya akan kehilangan beberapa karena mereka berada di garis dipecah menjadi potongan-potongan. Apakah ada cara untuk mengatasi ini? Menggunakan readlines tidak berfungsi dengan baik karena saya mendapat salah hitung
edo101
0

Bagaimana dengan ini? Bagilah file Anda menjadi potongan-potongan dan kemudian membacanya baris demi baris, karena ketika Anda membaca file, sistem operasi Anda akan men-cache baris berikutnya. Jika Anda membaca file baris demi baris, Anda tidak menggunakan informasi yang di-cache secara efisien.

Alih-alih, bagi file menjadi potongan-potongan dan muat seluruh potongan ke dalam memori dan kemudian lakukan pemrosesan Anda.

def chunks(file,size=1024):
    while 1:

        startat=fh.tell()
        print startat #file's object current position from the start
        fh.seek(size,1) #offset from current postion -->1
        data=fh.readline()
        yield startat,fh.tell()-startat #doesnt store whole list in memory
        if not data:
            break
if os.path.isfile(fname):
    try:
        fh=open(fname,'rb') 
    except IOError as e: #file --> permission denied
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
    except Exception as e1: #handle other exceptions such as attribute errors
        print "Unexpected error: {0}".format(e1)
    for ele in chunks(fh):
        fh.seek(ele[0])#startat
        data=fh.read(ele[1])#endat
        print data
Arohi Gupta
sumber
Ini terlihat menjanjikan. Apakah ini memuat berdasarkan byte atau garis? Saya takut garis akan terputus jika by byte .. bagaimana kita bisa memuat 1000 baris sekaligus dan memprosesnya?
Nikhil VJ
0

Terima kasih! Saya baru-baru ini dikonversi ke python 3 dan telah frustrasi dengan menggunakan readlines (0) untuk membaca file besar. Ini memecahkan masalah. Tetapi untuk mendapatkan setiap baris, saya harus melakukan beberapa langkah ekstra. Setiap baris didahului oleh "b '" yang saya kira itu dalam format biner. Menggunakan "decode (utf-8)" mengubahnya ascii.

Lalu saya harus menghapus "= \ n" di tengah setiap baris.

Lalu saya membagi garis di baris baru.

b_data=(fh.read(ele[1]))#endat This is one chunk of ascii data in binary format
        a_data=((binascii.b2a_qp(b_data)).decode('utf-8')) #Data chunk in 'split' ascii format
        data_chunk = (a_data.replace('=\n','').strip()) #Splitting characters removed
        data_list = data_chunk.split('\n')  #List containing lines in chunk
        #print(data_list,'\n')
        #time.sleep(1)
        for j in range(len(data_list)): #iterate through data_list to get each item 
            i += 1
            line_of_data = data_list[j]
            print(line_of_data)

Berikut adalah kode yang dimulai tepat di atas "data cetak" dalam kode Arohi.

John Haynes
sumber
0

Saya mendemonstrasikan pendekatan akses acak level byte paralel di sini dalam pertanyaan lain ini:

Mendapatkan jumlah baris dalam file teks tanpa garis baca

Beberapa jawaban yang sudah disediakan bagus dan ringkas. Saya suka beberapa dari mereka. Tapi itu sangat tergantung apa yang ingin Anda lakukan dengan data yang ada di file. Dalam kasus saya, saya hanya ingin menghitung baris, secepat mungkin pada file teks besar. Kode saya dapat dimodifikasi untuk melakukan hal-hal lain juga, seperti kode apa pun.

Geoffrey Anderson
sumber
0

Solusi terbaik yang saya temukan mengenai ini, dan saya mencobanya pada file 330 MB.

lineno = 500
line_length = 8
with open('catfour.txt', 'r') as file:
    file.seek(lineno * (line_length + 2))
    print(file.readline(), end='')

Di mana line_length adalah jumlah karakter dalam satu baris. Misalnya "abcd" memiliki panjang garis 4.

Saya telah menambahkan 2 dalam panjang garis untuk melewati karakter '\ n' dan pindah ke karakter berikutnya.

Ali Sajjad
sumber
-1

Ini mungkin berguna ketika Anda ingin bekerja secara paralel dan hanya membaca potongan data tetapi tetap bersih dengan baris baru.

def readInChunks(fileObj, chunkSize=1024):
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        while data[-1:] != '\n':
            data+=fileObj.read(1)
        yield data
Adam
sumber
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

semoga ini membantu.

Sainik Kr Mahata
sumber
5
Bukankah ini akan membaca seluruh file dalam memori? Pertanyaannya secara eksplisit menanyakan bagaimana cara menghindarinya, oleh karena itu ini tidak menjawab pertanyaan.
Fermi paradox