Jak czytać duże pliki tekstowe w Pythonie, wiersz po wierszu, bez ładowania do pamięci?

239

Muszę czytać duży plik, wiersz po wierszu. Powiedzmy, że plik ma więcej niż 5 GB i muszę przeczytać każdą linię, ale oczywiście nie chcę jej używać, readlines()ponieważ utworzy bardzo dużą listę w pamięci.

Jak działa poniższy kod dla tego przypadku? Czy xreadlinessam czyta jedno po drugim do pamięci? Czy potrzebne jest wyrażenie generatora?

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

f.next()  

Ponadto, co mogę zrobić, aby przeczytać to w odwrotnej kolejności, podobnie jak tailpolecenie Linux ?

Znalazłem:

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

i

Głowa, ogon i tył pytona czytane liniami pliku tekstowego

Oba działały bardzo dobrze!

Bruno Rocha - rochacbruno
źródło
Co mogę zrobić, aby przeczytać to z ogona? linia po linii, zaczynając od ostatniej linii.
Bruno Rocha - rochacbruno
to powinno być osobne pytanie
cmcginty,
1
zduplikowane stackoverflow.com/questions/5896079/…
cmcginty

Odpowiedzi:

310

Podałem tę odpowiedź, ponieważ Keith's, choć zwięzły, nie zamyka pliku jawnie

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
John La Rooy
źródło
30
pytanie wciąż brzmi: „for line in infile” załaduje moje 5 GB linii do pamięci? i, jak mogę czytać z ogona?
Bruno Rocha - rochacbruno
66
@ rochacbruno, czyta tylko jedną linię na raz. Kiedy czytany jest następny wiersz, poprzedni zostanie wyrzucony na śmietnik, chyba że zapiszesz odniesienie do niego gdzie indziej
John La Rooy
1
@ rochacbruno, Niestety czytanie linii w odwrotnej kolejności nie jest tak łatwe. Ogólnie rzecz biorąc, chciałbyś czytać od końca pliku w rozsądnych rozmiarach (kilobajty do megabajtów) i dzielić na znaki nowej linii (lub cokolwiek, na którym znak końca linii znajduje się na twojej platformie)
John La Rooy
4
Dzięki! Znalazłem rozwiązanie ogona stackoverflow.com/questions/5896079/...
Bruno Rocha - rochacbruno
1
@bejejakunal, Czy chodzi ci o to, że linia jest zbyt długa, aby załadować ją do pamięci? To niezwykłe w przypadku pliku tekstowego . Zamiast używać forpętli, która iteruje po liniach, możesz użyć chunk = infile.read(chunksize)do odczytu fragmentów o ograniczonym rozmiarze, niezależnie od ich zawartości. Musisz samodzielnie przeszukać fragmenty nowych linii.
John La Rooy,
60

Wszystko, co musisz zrobić, to użyć obiektu pliku jako iteratora.

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

Jeszcze lepsze jest użycie menedżera kontekstu w najnowszych wersjach Pythona.

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

Spowoduje to również automatyczne zamknięcie pliku.

Keith
źródło
2
To nie ładuje całego pliku do pamięci?
Bruno Rocha - rochacbruno
17

Podejście starej szkoły:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
źródło
2
drobna uwaga: dla bezpieczeństwa wyjątkowego zaleca się użycie wyrażenia „z”, w twoim przypadku „z otwartym (nazwa pliku,„ rt ”) jako fh:”
prokher 15.01.2015
16
@prokher: Tak, ale nazwałem to „starą szkołą”.
PTBNL
15

Lepiej zamiast tego użyj iteratora. Odpowiedni: http://docs.python.org/library/fileinput.html

Z dokumentów:

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

Pozwoli to uniknąć jednoczesnego kopiowania całego pliku do pamięci.

Mikola
źródło
Chociaż dokumenty pokazują fragment kodu jako „typowe użycie”, użycie go nie wywołuje close()metody zwracanego FileInputobiektu klasy po zakończeniu pętli - dlatego unikałbym używania go w ten sposób. W Pythonie 3.2 w końcu są one fileinputzgodne z protokołem menedżera kontekstu, który rozwiązuje ten problem (ale kod nadal nie byłby napisany w sposób pokazany na rysunku).
martineau
7

Oto, co robisz, jeśli w pliku nie ma nowych linii:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ariel Cabib
źródło
Chociaż podoba mi się ta metoda, ryzykujesz podzielenie wiersza w tekście na części. Widziałem to osobiście, co oznacza, że ​​jeśli szukasz w pliku pliku sstring tak jak ja, tęsknię za niektórymi, ponieważ linia, w której byli, była podzielona na części. Czy istnieje sposób na obejście tego? Korzystanie z readlines nie działało dobrze, ponieważ otrzymałem miscounts @Ariel Cabib
edo101
6

Spróbuj tego:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
jyoti das
źródło
proszę wytłumacz?
Nikhil VJ
3
Z oficjalnych docmunets Pythona: link Opcjonalny argument buforowania określa pożądany rozmiar bufora pliku: 0 oznacza bufor niebuforowany, 1 oznacza bufor buforowany, każda inna wartość dodatnia oznacza użycie bufora (w przybliżeniu) tego rozmiaru (w bajtach). Buforowanie ujemne oznacza użycie domyślnego systemu, który jest zwykle buforowany liniowo dla urządzeń tty i w pełni buforowany dla innych plików. Jeśli zostanie pominięty, zostanie użyta wartość domyślna systemu
jyoti das
Zapisałem mój dzień, w moim przypadku, z plikami> ~ 4GB z dwoma programami obsługi plików (jeden odczyt, drugi zapis) Python wisiał i teraz jest w porządku! Dzięki.
Xelt,
@ jyotidas Chociaż podoba mi się ta metoda, ryzykujesz, że linia w tekście zostanie podzielona na części. Widziałem to osobiście, co oznacza, że ​​jeśli szukasz w pliku pliku sstring tak jak ja, tęsknię za niektórymi, ponieważ linia, w której byli, była podzielona na części. Czy istnieje sposób na obejście tego? Korzystanie z readlines nie działało dobrze, ponieważ otrzymywałem
błędne informacje
3

Nie mogłem uwierzyć, że może to być tak proste, jak się wydaje odpowiedź @ john-la-rooy. Tak więc odtworzyłem cppolecenie za pomocą odczytu i zapisu wiersz po wierszu. CRAZY FAST.

#!/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
źródło
UWAGA: Ponieważ Python readlinestandaryzuje zakończenia linii, ma to efekt uboczny konwersji dokumentów z zakończeniami linii DOS-a \r\nna uniksowe zakończenia linii \n. Cały mój powód do poszukiwania tego tematu polegał na tym, że musiałem przekonwertować plik dziennika, który odbiera zbiór zakończeń linii (ponieważ programista ślepo korzystał z różnych bibliotek .NET). Byłem zszokowany, gdy stwierdziłem, że po pierwszym teście prędkości nie musiałem wracać i rstriplinii. To było już idealne!
Bruno Bronosky,
2

Projekt blask przeszedł długą drogę w ciągu ostatnich 6 lat. Ma prosty interfejs API obejmujący użyteczny podzbiór funkcji pand.

dask.dataframe dba o wewnętrzne dzielenie , obsługuje wiele równoległych operacji i pozwala łatwo eksportować wycinki z powrotem do pand w celu wykonania operacji w pamięci.

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
źródło
2

Oto kod ładowania plików tekstowych dowolnej wielkości bez powodowania problemów z pamięcią. Obsługuje pliki wielkości gigabajtów

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

pobierz plik data_loading_utils.py i zaimportuj go do swojego kodu

stosowanie

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)

linie_procesoweMetoda to funkcja zwrotna. Zostanie wywołany dla wszystkich linii, przy czym dane parametrów reprezentują jedną linię pliku na raz.

Możesz skonfigurować zmienną CHUNK_SIZE w zależności od konfiguracji sprzętowych maszyny.

Iyvin Jose
źródło
Chociaż podoba mi się ta metoda, ryzykujesz podzielenie wiersza w tekście na części. Widziałem to osobiście, co oznacza, że ​​jeśli szukasz w pliku pliku sstring tak jak ja, tęsknię za niektórymi, ponieważ linia, w której byli, była podzielona na części. Czy istnieje sposób na obejście tego? Korzystanie z readlines nie działało dobrze, ponieważ otrzymywałem
błędne informacje
0

Co powiesz na to? Podziel plik na części, a następnie przeczytaj wiersz po wierszu, ponieważ gdy czytasz plik, system operacyjny buforuje następny wiersz. Jeśli czytasz plik linia po linii, nie wykorzystujesz skutecznie buforowanych informacji.

Zamiast tego podziel plik na części i załaduj cały fragment do pamięci, a następnie wykonaj przetwarzanie.

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
źródło
To wygląda obiecująco. Czy to ładowanie bajtów czy linii? Obawiam się, że linie zostaną złamane, jeśli są bajty .. jak możemy załadować powiedzmy 1000 linii na raz i przetworzyć to?
Nikhil VJ
0

Dziękuję Ci! Niedawno przekonwertowałem na Python 3 i byłem sfrustrowany przez użycie readlines (0) do odczytu dużych plików. To rozwiązało problem. Ale aby uzyskać każdą linię, musiałem zrobić kilka dodatkowych kroków. Każda linia była poprzedzona literą „b”, która, jak sądzę, była w formacie binarnym. Użycie „dekodowania (utf-8)” zmieniło to ascii.

Następnie musiałem usunąć „= \ n” na środku każdej linii.

Następnie podzielę linie na nowej linii.

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)

Oto kod zaczynający się tuż nad „drukuj dane” w kodzie Arohi.

John Haynes
źródło
0

W tym innym pytaniu zademonstrowałem podejście losowego dostępu na poziomie bajtów.

Uzyskiwanie liczby wierszy w pliku tekstowym bez linii readline

Niektóre z udzielonych odpowiedzi są ładne i zwięzłe. Lubię niektóre z nich. Ale tak naprawdę zależy od tego, co chcesz zrobić z danymi zawartymi w pliku. W moim przypadku chciałem tylko jak najszybciej policzyć linie na dużych plikach tekstowych. Mój kod można oczywiście modyfikować, aby wykonywać także inne czynności, takie jak dowolny kod.

Geoffrey Anderson
źródło
0

Najlepsze rozwiązanie, jakie znalazłem, i wypróbowałem to na pliku 330 MB.

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

Gdzie długość_linii to liczba znaków w jednym wierszu. Na przykład „abcd” ma długość linii 4.

Dodałem 2 w linii, aby pominąć znak „\ n” i przejść do następnego znaku.

Ali Sajjad
źródło
-1

Może to być przydatne, gdy chcesz pracować równolegle i odczytywać tylko fragmenty danych, ale utrzymywać je w czystości dzięki nowym wierszom.

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
źródło
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

mam nadzieję że to pomoże.

Sainik Kr Mahata
źródło
5
Czy nie odczytałoby to całego pliku w pamięci? Pytanie wyraźnie pyta, jak tego uniknąć, dlatego nie odpowiada na to pytanie.
Paradoks Fermi