Як я можу читати великі текстові файли в Python, рядок за рядком, не завантажуючи його в пам'ять?


239

Мені потрібно прочитати великий файл, рядок за рядком. Скажемо, що файл має більше 5 Гб, і мені потрібно читати кожен рядок, але я, очевидно, не хочу його використовувати, 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/

і

" голова, хвіст і відстань пітона читаються рядками текстового файлу "

Обидва працювали дуже добре!


І що я можу зробити, щоб прочитати це з хвоста? рядок за рядком, починаючи з останнього рядка.
Bruno Rocha - rochacbruno

це має бути окремим питанням
cmcginty

Відповіді:


310

Я дав цю відповідь, оскільки Кейт, хоча лаконічний, не закриває файл явно

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

30
питання все ще полягає в тому, що "для рядка в інфіле" завантажуватимуть у пам'ять мої 5 Гб рядків? і, Як я можу читати з хвоста?
Bruno Rocha - rochacbruno

66
@rochacbruno, воно читає лише один рядок. Коли буде прочитаний наступний рядок, попередній буде зібраний сміття, якщо ви не зберегли посилання на нього десь інше
Джон Ла Рой,

1
@rochacbruno, Читання рядків у зворотному порядку, на жаль, не так просто зробити ефективно. Як правило, ви хочете прочитати з кінця файлу шматки розумних розмірів (кілобайт до мегабайт) та розділити на символи нового рядка (або все, що на вашій платформі закінчується символом рядка)
Джон Ла Рой,

4
Дякую! Я знайшов хвіст рішення stackoverflow.com/questions/5896079 / ...
Bruno Rocha - rochacbruno

1
@bawejakunal, Ви маєте на увазі, якщо рядок занадто довгий, щоб одразу завантажуватися в пам'ять? Це незвично для текстового файлу. Замість використання forциклу, який повторюється за лініями, ви можете chunk = infile.read(chunksize)читати фрагменти обмеженого розміру незалежно від їх вмісту. Вам доведеться самостійно шукати нові шматки.
Джон Ла Рой

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
Це не завантаження всього файлу в пам'ять?
Бруно Роча - rochacbruno

17

Старий підхід до школи:

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

2
незначне зауваження: для безпеки винятків рекомендується використовувати
команду

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сумісні з протоколом диспетчера контекстів, який вирішує цю проблему (але код все ще не буде написаний зовсім так, як показано).
мартіно

7

Ось що ви робите, якщо у файлі немає нових рядків:

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

Хоча мені подобається цей метод, ви ризикуєте розбити рядок у вашому тексті на шматки. Я бачив це особисто, це означає, що якщо ви шукаєте штрих у файлі, як я, я б пропустив деякі, оскільки рядок, на якому вони були, був розбитий на шматки. Чи є спосіб обійти це? Використання readlines не спрацювало добре, тому що я отримав недоліки @Ariel Cabib
edo101

6

Спробуйте:

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

Будь ласка, поясніть?
Nikhil VJ

3
З офіційного docmunets Python: link Необов'язковий аргумент буферизації визначає бажаний розмір буфера файлу: 0 означає небуферизований, 1 означає буферний рядок, будь-яке інше додатне значення означає використання буфера (приблизно) такого розміру (у байтах). Негативна буферизація означає використовувати системний типовий режим, який зазвичай буферизується для пристроїв tty та повністю буферується для інших файлів. Якщо пропущено, використовується системний замовчування
jyoti das

Зберегли мій день, у моєму випадку з файлами> ~ 4 ГБ з двома обробниками файлів (один читав, другий пише) пітон висів і тепер це добре! Дякую.
Xelt

@jyotidas Хоча мені цей метод подобається, ви ризикуєте розбити рядок у вашому тексті на шматки. Я бачив це особисто, це означає, що якщо ви шукаєте штрих у файлі, як я, я б пропустив деякі, оскільки рядок, на якому вони були, був розбитий на шматки. Чи є спосіб обійти це? Використання
рядків ліній

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)

ПРИМІТКА. Оскільки python readlineстандартизує закінчення рядків, це має побічний ефект перетворення документів із закінченнями рядків DOS в закінчення \r\nрядків Unix \n. Моя ціла причина пошуку цієї теми полягала в тому, що мені потрібно було конвертувати файл журналу, який отримує безліч закінчень рядків (адже розробник сліпо використовував різні бібліотеки .NET). Я був шокований, виявивши, що після мого початкового тесту на швидкість мені не потрібно було повертатися назад і rstripлінії. Це вже було ідеально!
Бруно Броноський

2

За останні 6 років проект пламенів пройшов довгий шлях. Він має простий API, що охоплює корисний набір функцій панди.

dask.dataframe піклується про збивання внутрішньо, підтримує безліч паралельних операцій та дозволяє легко експортувати фрагменти назад до панд для операцій в пам'яті.

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 залежно від конфігурації апаратного обладнання.


Хоча мені подобається цей метод, ви ризикуєте розбити рядок у вашому тексті на шматки. Я бачив це особисто, це означає, що якщо ви шукаєте штрих у файлі, як я, я б пропустив деякі, оскільки рядок, на якому вони були, був розбитий на шматки. Чи є спосіб обійти це? Використовуючи читання
ліній

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

Це виглядає перспективно. Це завантаження байтами чи рядками? Я боюся, що рядки будуть порушені, якщо це байтами .. як ми можемо завантажити сказати 1000 рядків одночасно і обробити це?
Nikhil VJ

0

Дякую! Нещодавно я перейшов на python 3 і був розчарований, використовуючи readlines (0) для читання великих файлів. Це вирішило проблему. Але щоб отримати кожен рядок, мені довелося зробити пару додаткових кроків. Кожному рядку передувало "b", яке, мабуть, було у двійковому форматі. Використання "декодування (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)

Ось код, що починається трохи вище "друку даних" у коді Арохі.


0

Тут я продемонстрував паралельний підхід на рівні випадкового доступу в байтах у цьому іншому запитанні:

Отримання кількості рядків у текстовому файлі без повторних рядків

Деякі з наданих відповідей є приємними та стислими. Мені подобаються деякі з них. Але це дійсно залежить від того, що ви хочете зробити з даними, які є у файлі. У моєму випадку я просто хотів підрахувати рядки як можна швидше у великих текстових файлах. Мій код можна змінити, щоб робити інші речі, звичайно, як і будь-який код.


0

Я знайшов найкраще рішення щодо цього, і я спробував це на файлі 330 Мб.

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
Чи не прочитав би це весь файл в пам'яті? Питання чітко запитує, як цього уникнути, тому це не відповідає на питання.
Парадокс Фермі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.