ฉันจะอ่านไฟล์ข้อความขนาดใหญ่ใน Python ทีละบรรทัดโดยไม่โหลดลงในหน่วยความจำได้อย่างไร


239

ฉันต้องอ่านไฟล์ขนาดใหญ่ทีละบรรทัด ให้บอกว่าไฟล์มีมากกว่า 5GB และฉันต้องอ่านแต่ละบรรทัด แต่เห็นได้ชัดว่าฉันไม่ต้องการใช้readlines()เพราะจะสร้างรายการที่มีขนาดใหญ่มากในหน่วยความจำ

รหัสด้านล่างจะทำงานอย่างไรสำหรับกรณีนี้ เป็นxreadlinesตัวเองอ่านหนึ่งโดยหนึ่งในหน่วยความจำ? นิพจน์ตัวสร้างจำเป็นหรือไม่?

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

f.next()  

นอกจากนี้ฉันต้องทำอย่างไรเพื่ออ่านสิ่งนี้ในลำดับย้อนกลับเช่นเดียวกับtailคำสั่งLinux

ฉันพบ:

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

และ

" python head, tail และ backward read โดยบรรทัดของไฟล์ข้อความ "

ทั้งสองทำงานได้ดีมาก!


และฉันจะทำอย่างไรเพื่ออ่านสิ่งนี้จากหาง ทีละบรรทัดเริ่มต้นในบรรทัดสุดท้าย
Bruno Rocha - rochacbruno

นี่ควรเป็นคำถามแยกต่างหาก
cmcginty

คำตอบ:


310

ฉันให้คำตอบนี้เพราะ Keith ในขณะที่รวบรัดไม่ปิดไฟล์อย่างชัดเจน

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

30
คำถามยังคงเป็น "สำหรับสายใน infile" จะโหลด 5GB ของบรรทัดในหน่วยความจำ และฉันจะอ่านจากหางได้อย่างไร
Bruno Rocha - rochacbruno

66
@rochacbruno อ่านครั้งละหนึ่งบรรทัดเท่านั้น เมื่ออ่านบรรทัดถัดไปบรรทัดก่อนหน้านี้จะถูกรวบรวมขยะเว้นแต่คุณจะเก็บข้อมูลอ้างอิงไว้ที่อื่น
John La Rooy

1
@rochacbruno การอ่านบรรทัดในลำดับย้อนกลับไม่ใช่เรื่องง่ายที่จะทำอย่างมีประสิทธิภาพน่าเสียดาย โดยทั่วไปแล้วคุณต้องการอ่านจากจุดสิ้นสุดของไฟล์เป็นชิ้นขนาดเหมาะสม (กิโลไบต์ถึงเมกะไบต์) และแยกตัวอักขระขึ้นบรรทัดใหม่ (หรืออะไรก็ตามที่อักขระสิ้นสุดบรรทัดอยู่บนแพลตฟอร์มของคุณ)
John La Rooy

4
ขอบคุณ! ฉันพบโซลูชันหางstackoverflow.com/questions/5896079/…
Bruno Rocha - rochacbruno

1
@bawejakunal, คุณหมายถึงว่าสายยาวเกินไปที่จะโหลดลงในหน่วยความจำได้หรือไม่? มันผิดปกติสำหรับไฟล์ข้อความ แทนที่จะใช้forลูปที่วนรอบบรรทัดคุณสามารถใช้chunk = infile.read(chunksize)เพื่ออ่านกลุ่มขนาด จำกัด โดยไม่คำนึงถึงเนื้อหา คุณจะต้องค้นหาข้อมูลในส่วนของบรรทัดใหม่ด้วยตัวเอง
John La Rooy

60

สิ่งที่คุณต้องทำคือใช้วัตถุไฟล์เป็นตัววนซ้ำ

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

ยิ่งไปกว่านั้นกำลังใช้ตัวจัดการบริบทในเวอร์ชัน Python ล่าสุด

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

วิธีนี้จะปิดไฟล์โดยอัตโนมัติเช่นกัน


2
ที่ไม่ได้โหลดไฟล์ทั้งหมดในหน่วยความจำ?
Bruno Rocha - rochacbruno

17

แนวทางโรงเรียนเก่า:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()

2
คำพูดเล็กน้อย: เพื่อความปลอดภัยยกเว้นขอแนะนำให้ใช้ 'กับ' คำสั่งในกรณีของคุณ "กับ open (ชื่อไฟล์, 'rt') เป็น fh:"
prokher

16
@prokher: ใช่ แต่ฉันเรียกสิ่งนี้ว่า "โรงเรียนเก่า"
PTBNL

15

คุณควรใช้ตัววนซ้ำแทนดีกว่า ที่เกี่ยวข้อง: http://docs.python.org/library/fileinput.html

จากเอกสาร:

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

วิธีนี้จะหลีกเลี่ยงการคัดลอกไฟล์ทั้งหมดไปยังหน่วยความจำพร้อมกัน


แม้ว่าเอกสารจะแสดงตัวอย่างเป็น "การใช้งานทั่วไป" แต่การใช้มันไม่ได้เรียกclose()วิธีการของFileInputคลาสอ็อบเจ็กต์ที่ส่งคืนเมื่อลูปเสร็จสิ้นดังนั้นฉันจะหลีกเลี่ยงการใช้วิธีนี้ ใน Python 3.2 ในที่สุดพวกเขาก็fileinputเข้ากันได้กับโพรโทคอจัดการบริบทซึ่งแก้ไขปัญหานี้ (แต่รหัสยังคงไม่ได้เขียนค่อนข้างแสดง)
martineau

7

นี่คือสิ่งที่คุณทำถ้าคุณไม่มีบรรทัดใหม่ในไฟล์:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)

ในขณะที่ฉันชอบวิธีนี้คุณเสี่ยงต่อการมีบรรทัดในข้อความของคุณแบ่งเป็นชิ้น ๆ ฉันเห็นสิ่งนี้เป็นการส่วนตัวซึ่งหมายความว่าหากคุณกำลังค้นหา sstring ในไฟล์เหมือนฉันฉันจะพลาดบางอย่างเพราะบรรทัดที่พวกเขาอยู่ที่ถูกแบ่งออกเป็นชิ้น ๆ มีวิธีที่จะได้รับรอบนี้หรือไม่? การใช้ readlines ทำงานได้ไม่ดีเท่าที่ฉันคิดผิด @Ariel Cabib
edo101

6

โปรดลองสิ่งนี้:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line

กรุณาอธิบาย?
Nikhil VJ

3
จาก docmunets อย่างเป็นทางการของ Python: การเชื่อมโยง อาร์กิวเมนต์บัฟเฟอร์ทางเลือกระบุขนาดบัฟเฟอร์ของไฟล์ที่ต้องการ: 0 หมายถึงไม่บัฟเฟอร์, 1 หมายถึงบัฟเฟอร์บรรทัด, ค่าบวกอื่น ๆ หมายถึงการใช้บัฟเฟอร์ (โดยประมาณ) ขนาดนั้น (เป็นไบต์) การบัฟเฟอร์เชิงลบหมายถึงการใช้ค่าเริ่มต้นของระบบซึ่งโดยปกติจะเป็นบรรทัดบัฟเฟอร์สำหรับอุปกรณ์ tty และบัฟเฟอร์เต็มสำหรับไฟล์อื่น ๆ หากไม่ใช้งานระบบจะใช้ค่าเริ่มต้น
jyoti das

บันทึกวันของฉันในกรณีของฉันด้วยไฟล์ ~ 4gb พร้อมตัวจัดการไฟล์สองตัว (อ่านหนึ่งเขียนอีกอัน) หลามหยุดทำงานและตอนนี้ก็ใช้ได้! ขอบคุณ
Xelt

@jyotidas ในขณะที่ฉันชอบวิธีนี้คุณมีความเสี่ยงที่จะมีบรรทัดในข้อความของคุณแตกเป็นชิ้น ๆ ฉันเห็นสิ่งนี้เป็นการส่วนตัวซึ่งหมายความว่าหากคุณกำลังค้นหา sstring ในไฟล์เหมือนฉันฉันจะพลาดบางอย่างเพราะบรรทัดที่พวกเขาอยู่ที่ถูกแบ่งออกเป็นชิ้น ๆ มีวิธีที่จะได้รับรอบนี้หรือไม่? การใช้ readlines ทำงานได้ไม่ดีเท่าที่ฉันมีความผิดพลาด
edo101

3

ฉันไม่อยากจะเชื่อว่ามันอาจจะง่ายเหมือนคำตอบของ @ john-la-rooy ทำให้ดูเหมือน ดังนั้นฉันจึงสร้างcpคำสั่งใหม่โดยใช้การอ่านและเขียนบรรทัด มันเร็วบ้าคลั่ง

#!/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)

หมายเหตุ: เนื่องจากหลามreadlineมาตรฐานสายปลายนี้มีผลข้างเคียงของการแปลงเอกสารกับ DOS ปลายสายของการใช้ระบบปฏิบัติการยูนิกซ์ปลายสายของ\r\n \nเหตุผลทั้งหมดของฉันในการค้นหาหัวข้อนี้คือฉันต้องการแปลงไฟล์บันทึกที่ได้รับความสับสนของการสิ้นสุดบรรทัด (เนื่องจากผู้พัฒนาใช้ไลบรารี NET หลาย ๆ ตัว) ฉันรู้สึกตกใจที่พบว่าหลังจากการทดสอบความเร็วเริ่มต้นฉันไม่จำเป็นต้องกลับไปrstripที่บรรทัดอีก มันสมบูรณ์แบบแล้ว!
Bruno Bronosky

2

ลุกโชนโครงการมีวิธีมานานกว่า 6 ปี มันมี API ง่าย ๆ ที่ครอบคลุมส่วนย่อยของคุณสมบัติแพนด้า

dask.dataframeดูแล chunking ภายในสนับสนุนการดำเนินการแบบขนานจำนวนมากและช่วยให้คุณสามารถส่งออกชิ้นส่วนกลับไปที่แพนด้าได้อย่างง่ายดายสำหรับการดำเนินการในหน่วยความจำ

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

2

รหัสนี้สำหรับการโหลดไฟล์ข้อความทุกขนาดโดยไม่ทำให้เกิดปัญหาหน่วยความจำ รองรับไฟล์ขนาดกิกะไบต์

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

ดาวน์โหลดไฟล์data_loading_utils.pyและนำเข้าสู่รหัสของคุณ

การใช้

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)

เมธอดprocess_linesเป็นฟังก์ชันการเรียกกลับ มันจะถูกเรียกสำหรับทุกบรรทัดที่มีข้อมูลพารามิเตอร์แสดงหนึ่งบรรทัดเดียวของไฟล์ในเวลา

คุณสามารถกำหนดค่าตัวแปรCHUNK_SIZEขึ้นอยู่กับการกำหนดค่าฮาร์ดแวร์ของเครื่อง


ในขณะที่ฉันชอบวิธีนี้คุณเสี่ยงต่อการมีบรรทัดในข้อความของคุณแบ่งเป็นชิ้น ๆ ฉันเห็นสิ่งนี้เป็นการส่วนตัวซึ่งหมายความว่าหากคุณกำลังค้นหา sstring ในไฟล์เหมือนฉันฉันจะพลาดบางอย่างเพราะบรรทัดที่พวกเขาอยู่ที่ถูกแบ่งออกเป็นชิ้น ๆ มีวิธีที่จะได้รับรอบนี้หรือไม่? การใช้ readlines ทำงานได้ไม่ดีเท่าที่ฉันมีความผิดพลาด
edo101

0

แล้วเรื่องนี้ล่ะ แบ่งไฟล์ของคุณเป็นชิ้น ๆ แล้วอ่านทีละบรรทัดเพราะเมื่อคุณอ่านไฟล์ระบบปฏิบัติการของคุณจะแคชบรรทัดถัดไป หากคุณกำลังอ่านไฟล์ทีละบรรทัดคุณไม่ได้ใช้ข้อมูลแคชอย่างมีประสิทธิภาพ

ให้แบ่งไฟล์ออกเป็นชิ้น ๆ แล้วโหลดทั้งก้อนลงในหน่วยความจำจากนั้นทำการประมวลผลของคุณ

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

สิ่งนี้ดูมีแนวโน้ม นี่คือการโหลดโดยไบต์หรือตามบรรทัด? ฉันกลัวว่าเส้นจะแตกถ้าเป็นไบต์ .. เราจะโหลดจำนวน 1,000 บรรทัดในแต่ละครั้งและประมวลผลได้อย่างไร?
Nikhil VJ

0

ขอบคุณ! ฉันเพิ่งแปลงเป็นงูหลาม 3 และได้รับความผิดหวังโดยใช้ readlines (0) เพื่ออ่านไฟล์ขนาดใหญ่ วิธีนี้แก้ปัญหาได้ แต่เพื่อให้ได้แต่ละบรรทัดฉันต้องทำสองขั้นตอนเพิ่มเติม แต่ละบรรทัดนำหน้าด้วย "b '" ซึ่งฉันเดาว่ามันอยู่ในรูปแบบไบนารี การใช้ "decode (utf-8)" เปลี่ยนเป็น ascii

จากนั้นฉันต้องลบ "= \ n" ที่อยู่ตรงกลางของแต่ละบรรทัด

จากนั้นฉันก็แบ่งบรรทัดที่บรรทัดใหม่

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)

นี่คือรหัสที่เริ่มต้นเหนือ "ข้อมูลการพิมพ์" ในรหัสของ Arohi


0

ฉันแสดงให้เห็นถึงวิธีการเข้าถึงแบบสุ่มในระดับไบต์คู่ขนานที่นี่ในคำถามอื่น ๆ นี้:

รับจำนวนบรรทัดในไฟล์ข้อความโดยไม่มี readlines

คำตอบบางส่วนที่ให้ไว้นั้นดีและกระชับ ฉันชอบบางคน แต่ขึ้นอยู่กับสิ่งที่คุณต้องการทำกับข้อมูลที่อยู่ในไฟล์ ในกรณีของฉันฉันแค่ต้องการนับบรรทัดเร็วที่สุดเท่าที่เป็นไปได้ในไฟล์ข้อความขนาดใหญ่ รหัสของฉันสามารถแก้ไขได้เพื่อทำสิ่งอื่นเช่นกันเช่นรหัสใด ๆ


0

ทางออกที่ดีที่สุดที่ฉันพบเกี่ยวกับเรื่องนี้และฉันได้ลองกับไฟล์ขนาด 330 MB

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

โดยที่ line_length คือจำนวนอักขระในบรรทัดเดียว ตัวอย่างเช่น "abcd" มีความยาวบรรทัด 4

ฉันได้เพิ่มความยาวบรรทัด 2 เพื่อข้ามอักขระ '\ n' และย้ายไปที่อักขระถัดไป


-1

สิ่งนี้อาจมีประโยชน์เมื่อคุณต้องการทำงานแบบขนานและอ่านข้อมูลเพียงชิ้นเดียว แต่รักษาความสะอาดด้วยบรรทัดใหม่

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

-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

หวังว่านี่จะช่วยได้


5
นี่จะไม่อ่านไฟล์ทั้งหมดในหน่วยความจำไหม? คำถามถามวิธีหลีกเลี่ยงอย่างชัดเจนดังนั้นสิ่งนี้จะไม่ตอบคำถาม
Fermi บุคคลที่ผิดธรรมดา
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.