Як читати великий файл - рядок за рядком?


536

Я хочу переглядати кожен рядок цілого файлу. Один із способів зробити це - прочитати весь файл, зберегти його до списку, а потім перейти через лінію інтересу. Цей метод використовує багато пам’яті, тому я шукаю альтернативу.

Мій код поки що:

for each_line in fileinput.input(input_file):
    do_something(each_line)

    for each_line_again in fileinput.input(input_file):
        do_something(each_line_again)

Виконання цього коду видає повідомлення про помилку: device active.

Будь-які пропозиції?

Мета - обчислити парну схожість рядків, тобто для кожного рядка у файлі, я хочу обчислити відстань Левенштейна з кожним іншим рядком.


4
Чому для кожного рядка потрібно знову прочитати весь файл? Можливо, якщо ви сказали, що ви намагаєтеся досягти, хтось може запропонувати кращий підхід.
JJJ

Відповіді:


1269

Правильний, повністю пітонічний спосіб читання файлу полягає в наступному:

with open(...) as f:
    for line in f:
        # Do something with 'line'

Оператор withобробляє відкриття та закриття файлу, у тому числі, якщо у внутрішньому блоці порушено виключення. Розглядає for line in fфайловий об'єкт fяк ітерабельний, який автоматично використовує захищений введення / виведення та управління пам'яттю, тому вам не доведеться турбуватися про великі файли.

Має бути один - і бажано лише один - очевидний спосіб зробити це.


14
так, це найкраща версія з python 2.6 та вище
Simon Bergot

3
Особисто я віддаю перевагу генераторам і кореспонденціям для роботи з потоками даних.
jldupont

4
що було б найкращою стратегією, якщо файл - це величезний текстовий файл, але з одним рядком і ідеєю є обробка слів?
mfcabrera

4
Може хтось пояснить, як for line in f:це працює? Я маю на увазі, як можлива ітерація над файловим об’єктом?
hackcks

11
Якщо ви повторюєте об'єкт, Python шукає у списку методів об'єкта спеціальний під назвою __iter__, який повідомляє йому, що робити. Файлові об’єкти визначають цей спеціальний метод для повернення ітератора через рядки. (Грубо.)
Катріель

130

Два ефективні способи пам'яті в ранжированому порядку (перший найкращий) -

  1. використання with- підтримується з python 2.5 та вище
  2. використання, yieldякщо ви дійсно хочете мати контроль над кількістю прочитаного

1. використання with

withце приємний та ефективний пітонічний спосіб читання великих файлів. переваги - 1) файл-об'єкт автоматично закривається після виходу з withблоку виконання. 2) обробка винятків всередині withблоку. 3) forцикл пам'яті ітераціює через fфайл об'єктний рядок за рядком. внутрішньо він робить буферизований IO (оптимізований для дорогих операцій з IO) та управління пам'яттю.

with open("x.txt") as f:
    for line in f:
        do something with data

2. використання yield

Іноді можна захотіти більш тонкого контролю над кількістю прочитаного в кожній ітерації. У такому випадку використовуйте iter & урожай . Зауважте, що при цьому методі явно потрібно закрити файл наприкінці.

def readInChunks(fileObj, chunkSize=2048):
    """
    Lazy function to read a file piece by piece.
    Default chunk size: 2kB.
    """
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        yield data

f = open('bigFile')
for chuck in readInChunks(f):
    do_something(chunk)
f.close()

Підводні камені та заради повноти - наведені нижче методи не настільки гарні чи не такі вишукані для читання великих файлів, але прочитайте будь ласка, щоб зрозуміти всебічно.

У Python найпоширенішим способом зчитування рядків з файлу є виконання наступного:

for line in open('myfile','r').readlines():
    do_something(line)

Однак, коли це зроблено, readlines()функція (те саме стосується read()функції) завантажує весь файл у пам'ять, а потім повторює його. Трохи кращим підходом (для перших згаданих двох методів є найкращим) для великих файлів є використання fileinputмодуля наступним чином:

import fileinput

for line in fileinput.input(['myfile']):
    do_something(line)

fileinput.input()виклик читає рядок послідовно, але не зберігає їх в пам'яті після того, як вони були прочитані або навіть просто так це, так як fileв пітона ітерації.

Список літератури

  1. Python із заявою

9
-1 Це взагалі ніколи не годиться робити for line in open(...).readlines(): <do stuff>. Чому б ти ?! Ви щойно втратили всю користь розумного буферизованого ітератора Python IO без жодної вигоди.
Катріель

5
@Srikar: є час і місце для надання всіх можливих рішень проблеми; вчити початківця, як робити введення файлів, це не так. Правильна відповідь похована внизу довгого допису, повного неправильних відповідей, це не дуже добре.
Катріель

6
@Srikar: Ви можете зробити свою публікацію значно кращою, поставивши правильний шлях вгорі, потім зазначивши readlinesта пояснивши, чому це не добре робити (адже він читає файл у пам'ять), потім пояснюючи, що fileinputробить модуль і чому ви можливо, захочете використовувати його над іншими методами, потім пояснюючи, як відбивання файлу робить IO кращим і наводячи приклад функції збивання (але згадуючи, що Python робить це вже для вас, тому вам не потрібно). Але давати п’ять способів вирішити просту проблему, чотири з яких помиляються в даному випадку, - це не добре.
Катріель

2
Що б ви не додали заради повноти, додайте останнє, а не перше. Спочатку покажіть правильний шлях.
m000

6
@katrielalex переглянув мою відповідь і виявив, що це вимагає реструктуризації. Я бачу, як попередня відповідь могла викликати плутанину. Сподіваємось, це стане зрозумілим для майбутніх користувачів.
Шрікар Аппалараджу

37

Щоб зняти нові рядки:

with open(file_path, 'rU') as f:
    for line_terminated in f:
        line = line_terminated.rstrip('\n')
        ...

З універсальної підтримкою нового рядка всі рядки файлу текст буде здаватися завершуватися '\n', незалежно термінатори в файлі, '\r', '\n'або '\r\n'.

EDIT - Щоб вказати універсальну підтримку нового рядка:

  • Python 2 на Unix - open(file_path, mode='rU')- обов'язковий [спасибі @Dave ]
  • Python 2 у Windows - open(file_path, mode='rU')- необов’язково
  • Python 3 - open(file_path, newline=None)- необов'язково

newlineПараметр підтримується лише в Python 3 і по замовчуванням None. В modeпараметрах по замовчуванням 'r'у всіх випадках. У UPython 3. застаріле значення є. У Python 2 у Windows з'являється переклад \r\nна якийсь інший механізм \n.

Документи:

Щоб зберегти нативні термінатори:

with open(file_path, 'rb') as f:
    with line_native_terminated in f:
        ...

Бінарний режим все ще може розібрати файл у рядки in. Кожен рядок матиме у файлі будь-які термінатори.

Завдяки @katrielalex «s відповідь , Python, відкритий () документ, і IPython експерименти.


1
На Python 2.7 мені довелося open(file_path, 'rU')включити універсальні нові рядки.
Дейв

17

це можливий спосіб читання файлу в python:

f = open(input_file)
for line in f:
    do_stuff(line)
f.close()

вона не виділяє повного списку. Він перебирає лінії.


2
Хоча це працює, це точно не канонічний шлях. Канонічним способом є використання контекстної обгортки, наприклад with open(input_file) as f:. Це економить f.close()і гарантує, що ви випадково не забудете його закрити. Запобігає витоку пам'яті і все, що дуже важливо при читанні файлів.
Щогли

1
Як сказав @Mast, це не є канонічним способом, тому виступаю за це.
azuax

12

Якийсь контекст перед тим, звідки я родом. Фрагменти коду в кінці.

Коли я можу, я вважаю за краще використовувати інструмент з відкритим кодом, як H2O, щоб робити високоефективні паралельні зчитування файлів CSV, але цей інструмент обмежений у наборі функцій. Я закінчую писати багато коду, щоб створити трубопроводи з інформацією, перш ніж подавати в кластер H2O для належного нагляду за навчанням.

Я читав такі файли, як набір даних HIGGS 8 ГБ, з UCI repo і навіть 40 ГБ файлів CSV для цілей наукових даних значно швидше, додаючи багато паралелізму з об'єктом об’єднання пулу та картою багатопроцесорної бібліотеки. Наприклад, кластеризація з найближчим пошуком сусідів, а також алгоритми кластеризації DBSCAN та Маркова вимагає певної паралельної точності програмування для обходу деяких серйозно складних проблем з пам'яттю та настінним годинником.

Я, як правило, люблю розбивати файл на рядок, використовуючи спочатку інструменти gnu, а потім глобальну маску файлів, щоб знайти їх і паралельно прочитати в програмі python. Я зазвичай використовую щось на зразок 1000+ часткових файлів. Виконання цих хитрощів надзвичайно допомагає з обмеженням швидкості обробки та обмеження пам’яті.

Pandas dataframe.read_csv є однопотоковою, тому ви можете виконати ці хитрощі, щоб зробити панди досить швидкими, виконавши карту () для паралельного виконання. Ви можете використовувати htop, щоб побачити, що із простими старими послідовними пандами dataframe.read_csv, 100% процесор на одному ядрі є фактичним вузьким місцем у pd.read_csv, а не на диску взагалі.

Слід додати, що я використовую SSD на швидкій шині відеокарт, а не обертовий HD на шині SATA6, плюс 16 ядер процесора.

Крім того, інша методика, яку я виявив, відмінно працює в деяких додатках, - це паралельний файл CSV, який читає все в одному гігантському файлі, запускаючи кожного працівника з різним зміщенням у файл, а не попередньо розбиваючи один великий файл на багато файлів частини. Використовуйте файл python search () та скажіть () у кожному паралельному працівнику, щоб прочитати великий текстовий файл у смугах, у різних байтових зміщеннях початкового та кінцевого байтів у великому файлі, все одночасно. Ви можете зробити регулярне вираження значень у байтах та повернути кількість ліній каналу. Це часткова сума. Нарешті підсумуйте часткові суми, щоб отримати загальну суму, коли функція карти повернеться після завершення роботи.

Нижче наведено кілька прикладів орієнтирів, що використовують трюк зміщення паралельних байтів:

Я використовую 2 файли: HIGGS.csv - 8 ГБ. Це з сховища машинного навчання UCI. all_bin .csv становить 40,4 Гб і є від мого поточного проекту. Я використовую 2 програми: програму GNU wc, яка постачається з Linux, і чисту програму python fastread.py, яку я розробив.

HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv

HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb  2 09:00 all_bin.csv

ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496

real    0m8.920s
user    1m30.056s
sys 2m38.744s

In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175

Це приблизно 4,5 Гб / с, або 45 Гбіт / с, швидкість шумування файлів. Мій друг, це не жорсткий диск, що обертається. Це насправді SSD Samsung Pro 950.

Нижче наведено орієнтир швидкості для того самого файлу, який підраховується рядком gnu wc, чистою програмою, складеною на C.

Що здорово - це ви можете бачити, що моя чиста програма python в основному відповідала швидкості компільованої програми C gnu wc. Python інтерпретується, але компілюється C, так що це досить цікавий подвиг швидкості, я думаю, ви погодилися б. Звичайно, wc дійсно потрібно змінити на паралельну програму, і тоді він справді бив шкарпетки з моєї програми python. Але, як сьогодні, gnu wc - це лише послідовна програма. Ви робите все, що можете, і пітон сьогодні може робити паралельно. Компіляція Cython може допомогти мені (на деякий інший час). Також файли з картографічною пам'яттю ще не досліджені.

HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv

real    0m8.807s
user    0m1.168s
sys 0m7.636s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s

Висновок: швидкість хороша для чистої програми python порівняно з програмою C. Однак недостатньо добре використовувати програму чистого пітона над програмою C, принаймні для цілей лінійного обліку. Зазвичай техніку можна використовувати для іншої обробки файлів, тому цей код python все ще хороший.

Запитання: Чи збільшить регулярний вираз лише один раз і передасть його всім працівникам, покращить швидкість? Відповідь: Попередня компіляція Regex НЕ допомагає в цій програмі. Я гадаю, що причина полягає в тому, що панує перевага процесів серіалізації та створення для всіх працівників.

І ще одна річ. Чи допомагає паралельне читання файлів CSV? Чи є вузьким місцем диск, чи це процесор? Багато так званих відповідей із найкращими рейтингами щодо stackoverflow містять загальну мудрість розробника, що вам потрібно лише один потік, щоб прочитати файл, що найкраще ви можете зробити, кажуть вони. Але вони впевнені?

Давай дізнаємось:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

О так, так. Паралельне читання файлів працює досить добре. Ну там ви йдете!

Пс. Якщо хтось із вас захотів знати, що робити, якщо балансFactor був 2 при використанні одного робочого процесу? Ну, це жахливо:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000

real    1m37.077s
user    0m12.432s
sys 1m24.700s

Основні частини програми fastread.py python:

fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)


def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
    with open(fileName, 'r') as f:
        f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
        bytes = f.read(endByte - startByte + 1)
        cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
    return cnt

Def для PartitionDataToWorkers - це просто звичайний послідовний код. Я покинув це, якщо хтось інший захоче попрацювати над тим, що таке паралельне програмування. Я безкоштовно роздав складніші частини: перевірений і працюючий паралельний код, для вашої переваги в навчанні.

Завдяки: проекту H2O з відкритим кодом, від Arno та Cliff та персоналу H2O за їх чудове програмне та навчальне відео, яке дало мені натхнення для цього чистого високоефективного зчитувача паралельних байтів з компенсацією паралельних байтів, як показано вище. H2O робить паралельне читання файлів за допомогою Java, може викликати програми python та R, і божеволіє швидше, швидше за все на планеті при читанні великих файлів CSV.


Паралельні шматки - це в основному це. Крім того, я думаю, що SSD і Flash - єдині сумісні пристрої зберігання даних з цією технікою. Спінінг HD навряд чи може бути сумісним.
Джеффрі Андерсон

1
Як ви обліковували файли диска кешування ОС?
JamesThomasMoon1979

5

Katrielalex надав спосіб відкрити та прочитати один файл.

Однак, як піде ваш алгоритм, він читає весь файл для кожного рядка файлу. Це означає, що загальна кількість читання файлу - і обчислення відстані Левенштейна - буде виконано N * N, якщо N - кількість рядків у файлі. Оскільки ви стурбовані розміром файлу і не хочете зберігати його в пам'яті, я стурбований результатом квадратичного виконання . Ваш алгоритм входить до класу алгоритмів O (n ^ 2), який часто можна вдосконалити за допомогою спеціалізації.

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

Скільки рядків мають ваші файли та на якій машині (потужність пам’яті та процесора) повинен працювати ваш алгоритм, і який термін переноситься?

Код виглядатиме так:

with f_outer as open(input_file, 'r'):
    for line_outer in f_outer:
        with f_inner as open(input_file, 'r'):
            for line_inner in f_inner:
                compute_distance(line_outer, line_inner)

Але питання полягають у тому, як ви зберігаєте відстані (матрицю?) І чи можете ви отримати перевагу під час підготовки, наприклад, зовнішньої лінії до обробки або кешування деяких проміжних результатів для повторного використання.


Моя думка, що ця публікація не містить відповіді на запитання, лише ще кілька питань! ІМО це краще підходить як коментар.
Катріель

1
@katriealex: Помилка. Дивно. Ви бачили вкладені петлі, розширюючи власну відповідь, щоб відповідати актуальному питанню? Я можу видалити свої запитання тут зі своєї відповіді, і є ще достатньо вмісту, щоб гарантувати, що це дає - хоч і часткову - відповідь. Я також можу прийняти, якщо ви відредагуєте свою власну відповідь, щоб включити приклад вкладеного циклу - про що явно було поставлено запитання - і тоді я можу видалити власну відповідь щасливо. Але скоромовка - це те, чого я взагалі не отримую.
cfi

Справедливо; Я насправді не бачу демонстрації вкладених циклів як відповіді на питання, але я думаю, що це досить сильно орієнтовано на новачків. Downvote видалено.
Катріель

3
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • Відкрийте файл для читання (r)
  • Прочитайте весь файл і збережіть кожен рядок у списку (текст)
  • Проведіть список, друкуючи кожен рядок.

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

for line in text:
    if len(line) > 10:
        print line

1
Не найкраще для цього питання, але цей код в основному корисний у тому випадку, якщо ви шукаєте. Це "ковзання" (читання всього файлу одразу). Це був мій випадок, і google мене тут привів. +1. Крім того, для атомності, або якщо ви зайняли багато часу, обробка в циклі може закінчитися швидше, щоб прочитати весь файл
ntg

1
Крім того, трохи покращили код: 1. Закривати не потрібно після за допомогою: ( docs.python.org/2/tutorial/inputoutput.html , пошукайте "Це найкраща практика використання ключового слова ...") 2 Текст може бути оброблений після того, як файл буде прочитаний (навпроти з циклом ....)
ntg

2

З документації python для fileinput .input ():

Це повторюється за рядками всіх файлів, перелічених у sys.argv[1:], за замовчуванням, sys.stdinякщо список порожній

Далі, визначення функції:

fileinput.FileInput([files[, inplace[, backup[, mode[, openhook]]]]])

читаючи між рядків, це говорить мені, що filesможе бути список, щоб у вас вийшло щось на кшталт:

for each_line in fileinput.input([input_file, input_file]):
  do_something(each_line)

Дивіться тут для отримання додаткової інформації


2

Настійно рекомендую не використовувати завантаження файлів за замовчуванням, оскільки це жахливо повільно. Ви повинні вивчити функції numpy та функції IOpro (наприклад, numpy.loadtxt ()).

http://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html

https://store.continuum.io/cshop/iopro/

Тоді ви можете розбити свою парну операцію на шматки:

import numpy as np
import math

lines_total = n    
similarity = np.zeros(n,n)
lines_per_chunk = m
n_chunks = math.ceil(float(n)/m)
for i in xrange(n_chunks):
    for j in xrange(n_chunks):
        chunk_i = (function of your choice to read lines i*lines_per_chunk to (i+1)*lines_per_chunk)
        chunk_j = (function of your choice to read lines j*lines_per_chunk to (j+1)*lines_per_chunk)
        similarity[i*lines_per_chunk:(i+1)*lines_per_chunk,
                   j*lines_per_chunk:(j+1)*lines_per_chunk] = fast_operation(chunk_i, chunk_j) 

Практично завжди набагато швидше завантажувати дані в шматки, а потім робити матричні операції на ньому, ніж робити це по елементам !!


0

Потрібно часто читати великий файл з останнього читання позиції?

Я створив сценарій, який використовується для вирізання файлу Apache access.log кілька разів на день. Тому мені потрібно було встановити курсор позиції на останньому рядку, проаналізованому під час останнього виконання . З цією метою я використав file.seek()і file.seek()методи, що дозволяють зберігати курсор у файлі.

Мій код:

ENCODING = "utf8"
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))

# This file is used to store the last cursor position
cursor_position = os.path.join(CURRENT_FILE_DIR, "access_cursor_position.log")

# Log file with new lines
log_file_to_cut = os.path.join(CURRENT_FILE_DIR, "access.log")
cut_file = os.path.join(CURRENT_FILE_DIR, "cut_access", "cut.log")

# Set in from_line 
from_position = 0
try:
    with open(cursor_position, "r", encoding=ENCODING) as f:
        from_position = int(f.read())
except Exception as e:
    pass

# We read log_file_to_cut to put new lines in cut_file
with open(log_file_to_cut, "r", encoding=ENCODING) as f:
    with open(cut_file, "w", encoding=ENCODING) as fw:
        # We set cursor to the last position used (during last run of script)
        f.seek(from_position)
        for line in f:
            fw.write("%s" % (line))

    # We save the last position of cursor for next usage
    with open(cursor_position, "w", encoding=ENCODING) as fw:
        fw.write(str(f.tell()))

-2

Найкращий спосіб читати великий файл, рядок за рядком, - це використовувати функцію перерахування python

with open(file_name, "rU") as read_file:
    for i, row in enumerate(read_file, 1):
        #do something
        #i in line of that line
        #row containts all data of that line

3
Чому використання нумерації краще? Єдина перевага від прийнятої відповіді полягає в тому, що ви отримуєте індекс, який ОП не потрібен, і ви робите код менш читабельним.
fuyas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.