Python Büyük Dosya Okuma için Tembel Yöntem?


290

4GB'lık çok büyük bir dosyam var ve okumaya çalıştığımda bilgisayarım kilitleniyor. Bu yüzden parça parça okumak istiyorum ve her parçayı işledikten sonra işlenen parçayı başka bir dosyada saklayın ve sonraki parçayı okuyun.

yieldBu parçalar için herhangi bir yöntem var mı ?

Tembel bir yöntem olmasını isterdim .

Yanıtlar:


424

Tembel bir işlev yazmak için şunu kullanın yield:

def read_in_chunks(file_object, chunk_size=1024):
    """Lazy function (generator) to read a file piece by piece.
    Default chunk size: 1k."""
    while True:
        data = file_object.read(chunk_size)
        if not data:
            break
        yield data


with open('really_big_file.dat') as f:
    for piece in read_in_chunks(f):
        process_data(piece)

Başka bir seçenek kullanmak iterve bir yardımcı işlev olacaktır:

f = open('really_big_file.dat')
def read1k():
    return f.read(1024)

for piece in iter(read1k, ''):
    process_data(piece)

Dosya satır tabanıysa, dosya nesnesi zaten tembel bir satır oluşturucusudur:

for line in open('really_big_file.dat'):
    process_data(line)

Yani hat f = open('really_big_file.dat')sadece bellek tüketimi olmayan bir işaretçi mi? (Demek istediğim, kullanılan bellek dosya boyutuna bakılmaksızın aynı mı?) F.readline () yerine urllib.readline () kullanırsam performansı nasıl etkiler?
sumid

4
Posix tarafından meydan okunan Windows iş arkadaşlarımızla uyumluluk için open ('really_big_file.dat', 'rb') kullanmak için iyi bir uygulama.
Tal Weiss

6
rb@Tal Weiss'in bahsettiği gibi eksik ; ve bir file.close()ifadeyi kaçırmak ( with open('really_big_file.dat', 'rb') as f:aynı şeyi gerçekleştirmek için kullanabilir ; Başka bir kısa uygulama için buraya
cod3monk3y

4
@ cod3monk3y: metin ve ikili dosyalar farklı şeylerdir. Her iki tip de faydalıdır, ancak farklı durumlarda. Varsayılan (metin) modu burada yani yararlı olabilir 'rb'edilir değil eksik.
jfs

2
@ jf-sebastian: doğru, OP metinsel mi yoksa ikili veri mi okuduğunu belirtmedi. O üzerinde Python 2.7 kullanarak Ama eğer Windows'un ve bir ikili veri okuma, o unutursa belirterek kesinlikle değer 'b'onun veri olacak çok büyük olasılıkla bozuk . Dokümanlar'dan -Python on Windows makes a distinction between text and binary files; [...] it’ll corrupt binary data like that in JPEG or EXE files. Be very careful to use binary mode when reading and writing such files.
cod3monk3y

41

Bilgisayarınız, işletim sisteminiz ve python'unuz 64 bit ise, dosyanın içeriğini belleğe eşlemek ve dosyalara dizinler ve dilimlerle erişmek için mmap modülünü kullanabilirsiniz . Burada dokümantasyondan bir örnek:

import mmap
with open("hello.txt", "r+") as f:
    # memory-map the file, size 0 means whole file
    map = mmap.mmap(f.fileno(), 0)
    # read content via standard file methods
    print map.readline()  # prints "Hello Python!"
    # read content via slice notation
    print map[:5]  # prints "Hello"
    # update content using slice notation;
    # note that new content must have same size
    map[6:] = " world!\n"
    # ... and read again using standard file methods
    map.seek(0)
    print map.readline()  # prints "Hello  world!"
    # close the map
    map.close()

Bilgisayarınız, işletim sisteminiz veya python'unuz 32 bit ise , büyük dosyaları eşleştirmek adres alanınızın büyük bölümlerini ayırabilir ve bellek programınızı bırakabilir .


7
Bunun nasıl çalışması gerekiyor? 32 GB'lık bir dosyam varsa ne olur? 256 MB RAM'e sahip bir VM'deysem ne olur? Böyle büyük bir dosyayı eşleştirmek gerçekten iyi bir şey değildir.
Savino Sguera

4
Bu cevap -12 oyu hak ediyor. Büyük dosyalar için bunu kullanan herkesi öldürür.
Phyo Arkar Lwin

23
Bu, büyük dosyalar için bile 64 bit Python üzerinde çalışabilir. Dosya bellek eşlemesine rağmen, belleğe okunmaz, bu nedenle fiziksel bellek miktarı dosya boyutundan çok daha küçük olabilir.
pts

1
@SavinoSguera bir dosyayı eşleştirmek için fiziksel belleğin boyutu önemli mi?
Nick T

17
@ V3ss0n: 32 bit dosyayı 64 bit Python'da eşleştirmeye çalıştım. Çalışıyor (32GB'den daha az RAM'im var): Hem Dizi hem de dosya arabirimlerini kullanarak dosyanın başlangıcına, ortasına ve sonuna erişebilirim.
jfs

37

file.readlines() döndürülen satırlarda okunan satır sayısına yaklaşan isteğe bağlı bir boyut bağımsız değişkeni alır.

bigfile = open('bigfilename','r')
tmp_lines = bigfile.readlines(BUF_SIZE)
while tmp_lines:
    process([line for line in tmp_lines])
    tmp_lines = bigfile.readlines(BUF_SIZE)

1
özellikle büyük verileri daha küçük verilere bölmek için varsayılan karar ile birleştirildiğinde gerçekten harika bir fikir.
Frank Wang

4
Ben kullanmayı öneriyoruz .read()değil .readlines(). Dosya ikiliyse, satır sonları olmayacaktır.
Myers Carpenter

1
Dosya büyük bir dizeyse ne olur?
MattSom

28

Zaten çok sayıda iyi yanıt var, ancak dosyanızın tamamı tek bir satırdaysa ve yine de "satırları" (sabit boyutlu blokların aksine) işlemek istiyorsanız, bu cevaplar size yardımcı olmaz.

Zamanın% 99'u, dosyaları satır satır işlemek mümkündür. Daha sonra, bu cevapta önerildiği gibi , dosya nesnesinin kendisini tembel jeneratör olarak kullanabilirsiniz:

with open('big.csv') as f:
    for line in f:
        process(line)

Ancak, bir zamanlar satır ayırıcı aslında değil '\n'ama çok çok büyük (neredeyse) tek satır dosyası ile karşılaştım '|'.

  • Satır satır okuma bir seçenek değildi, ama yine de satır satır işlemek gerekiyordu.
  • Dönüştürme '|'için '\n'bu csv alanlarının bazı içerdiği için işleme, söz konusu da önce '\n'(serbest metin kullanıcı girişi).
  • Csv kütüphanesini kullanmak da reddedildi çünkü en azından lib'in ilk sürümlerinde , girişi satır satır okumak zor .

Bu tür durumlar için aşağıdaki snippet'i oluşturdum:

def rows(f, chunksize=1024, sep='|'):
    """
    Read a file where the row separator is '|' lazily.

    Usage:

    >>> with open('big.csv') as f:
    >>>     for r in rows(f):
    >>>         process(row)
    """
    curr_row = ''
    while True:
        chunk = f.read(chunksize)
        if chunk == '': # End of file
            yield curr_row
            break
        while True:
            i = chunk.find(sep)
            if i == -1:
                break
            yield curr_row + chunk[:i]
            curr_row = ''
            chunk = chunk[i+1:]
        curr_row += chunk

Sorunumu çözmek için başarıyla kullanabildim. Çeşitli yığın boyutları ile kapsamlı bir şekilde test edilmiştir.


Test paketi, kendilerini ikna etmek isteyenler için.

test_file = 'test_file'

def cleanup(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        os.unlink(test_file)
    return wrapper

@cleanup
def test_empty(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1_char_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_1_char(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1025_chars_1_row(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1025):
            f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1024_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1023):
            f.write('a')
        f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_1025_chars_1026_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1025):
            f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1026

@cleanup
def test_2048_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1022):
            f.write('a')
        f.write('|')
        f.write('a')
        # -- end of 1st chunk --
        for i in range(1024):
            f.write('a')
        # -- end of 2nd chunk
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_2049_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1022):
            f.write('a')
        f.write('|')
        f.write('a')
        # -- end of 1st chunk --
        for i in range(1024):
            f.write('a')
        # -- end of 2nd chunk
        f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

if __name__ == '__main__':
    for chunksize in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]:
        test_empty(chunksize)
        test_1_char_2_rows(chunksize)
        test_1_char(chunksize)
        test_1025_chars_1_row(chunksize)
        test_1024_chars_2_rows(chunksize)
        test_1025_chars_1026_rows(chunksize)
        test_2048_chars_2_rows(chunksize)
        test_2049_chars_2_rows(chunksize)

11
f = ... # file-like object, i.e. supporting read(size) function and 
        # returning empty string '' when there is nothing to read

def chunked(file, chunk_size):
    return iter(lambda: file.read(chunk_size), '')

for data in chunked(f, 65536):
    # process the data

GÜNCELLEME: Yaklaşım en iyi https://stackoverflow.com/a/4566523/38592 adresinde açıklanmaktadır.


Bu lekeler için iyi çalışır, ancak satırla ayrılmış içerik için iyi olmayabilir (işlemin satır satır ele alınması gereken CSV, HTML vb.)
cgseller

7

Python'un resmi belgelerine bakın https://docs.python.org/zh-cn/3/library/functions.html?#iter

Belki bu yöntem daha pitoniktir:

from functools import partial

"""A file object returned by open() is a iterator with
read method which could specify current read's block size"""
with open('mydata.db', 'r') as f_in:

    part_read = partial(f_in.read, 1024*1024)
    iterator = iter(part_read, b'')

    for index, block in enumerate(iterator, start=1):
        block = process_block(block)    # process block data
        with open(f'{index}.txt', 'w') as f_out:
            f_out.write(block)

3

Sanırım şöyle yazabiliriz:

def read_file(path, block_size=1024): 
    with open(path, 'rb') as f: 
        while True: 
            piece = f.read(block_size) 
            if piece: 
                yield piece 
            else: 
                return

for piece in read_file(path):
    process_piece(piece)

2

düşük itibar nedeniyle yorum yapmasına izin verilmiyor, ancak SilentGhosts çözümü file.readlines ([sizehint]) ile çok daha kolay olmalıdır

python dosya yöntemleri

edit: SilentGhost haklıdır, ancak bu daha iyi olmalıdır:

s = "" 
for i in xrange(100): 
   s += file.next()

tamam, üzgünüm, kesinlikle haklısın. ama belki de bu çözüm sizi daha mutlu edecektir;): s = "" i xrange (100) için: s + = file.next ()
sinzi

1
-1: Korkunç bir çözüm, bu, her satırda bellekte yeni bir dize oluşturmak ve okunan tüm dosya verilerini yeni dizeye kopyalamak anlamına gelir. En kötü performans ve bellek.
nosklo

neden tüm dosya verilerini yeni bir dizeye kopyalasın ki? Bir for döngüsünü bir dosyanın satırları üzerinde döngü oluşturmanın en verimli yolu yapmak için (çok yaygın bir işlem), next () yöntemi gizli bir okuma-önbellek kullanır.
sinzi

3
@sinzi: "s + =" veya birleştirme dizeleri her seferinde dizenin yeni bir kopyasını oluşturur, çünkü dize değişmez olduğundan yeni bir dize oluşturursunuz.
nosklo

1
@nosklo: bunlar uygulamanın ayrıntıları, liste kavranması yerinde kullanılabilir
SilentGhost

1

Biraz benzer bir durumdayım. Bayt cinsinden yığın boyutunu bilip bilmediğiniz açık değildir; Genellikle yapmam, ancak gerekli kayıt (satır) sayısı bilinir:

def get_line():
     with open('4gb_file') as file:
         for i in file:
             yield i

lines_required = 100
gen = get_line()
chunk = [i for i, j in zip(gen, range(lines_required))]

Güncelleme : Teşekkürler nosklo. Demek istediğim bu. Neredeyse işe yarıyor, sadece 'arasındaki' parçalar arasında bir çizgi kaybediyor.

chunk = [next(gen) for i in range(lines_required)]

Hile herhangi bir çizgi kaybetmeden mi, ama çok hoş görünmüyor.


1
bu sahte kod mu? işe yaramaz. Aynı zamanda kafa karıştırıcıdır, satır sayısını get_line işlevine isteğe bağlı bir parametre yapmalısınız.
nosklo

0

Satır satır işlemek için bu zarif bir çözümdür:

  def stream_lines(file_name):
    file = open(file_name)
    while True:
      line = file.readline()
      if not line:
        file.close()
        break
      yield line

Boş satır olmadığı sürece.


6
Bu, opensize zaten verdiklerine aşırı derecede karmaşık, daha az sağlam ve daha yavaş bir eşdeğerdir . Bir dosya zaten satırlarının üzerinde bir yineleyicidir.
abarnert

-2

aşağıdaki kodu kullanabilirsiniz.

file_obj = open('big_file') 

open () bir dosya nesnesi döndürür

sonra boyut almak için os.stat kullanın

file_size = os.stat('big_file').st_size

for i in range( file_size/1024):
    print file_obj.read(1024)

boyut 1024 ile çarpılmazsa tüm dosyayı
okumaz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.