web-dev-qa-db-fra.com

Méthode paresseuse pour lire un gros fichier en Python?

J'ai un très gros fichier de 4 Go et lorsque j'essaie de le lire, mon ordinateur se bloque. Je souhaite donc le lire pièce par pièce et, après avoir traité chaque pièce, stocke la pièce traitée dans un autre fichier et lit la pièce suivante.

Existe-t-il une méthode pour yield ces pièces?

J'aimerais avoir un méthode paresseuse.

253
Pratik Deoghare

Pour écrire une fonction paresseuse, utilisez simplement 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


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

Une autre option consisterait à utiliser iter et une fonction d'assistance:

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

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

Si le fichier est basé sur une ligne, l'objet fichier est déjà un générateur de lignes paresseux:

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

Si votre ordinateur, votre système d'exploitation et python sont en 64 bits , vous pouvez utiliser le module mmap mapper le contenu du fichier en mémoire et y accéder avec des index et des tranches. Voici un exemple tiré de la documentation:

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

Si votre ordinateur, votre système d'exploitation ou python sont en 32 bits , les fichiers de grande taille peuvent réserver de grandes parties de votre adresse. espace et mourir de faim votre programme de mémoire.

37
unbeknown

file.readlines () prend un argument optionnel de taille qui se rapproche du nombre de lignes lues dans les lignes retournées.

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)
27
Anshul

Il y a déjà beaucoup de bonnes réponses, mais j'ai récemment rencontré un problème similaire et la solution dont j'avais besoin ne figure pas dans la liste. J'ai donc pensé pouvoir compléter ce fil.

80% du temps, je dois lire les fichiers ligne par ligne. Ensuite, comme suggéré dans ce réponse , vous voulez utiliser l'objet fichier lui-même comme générateur différé:

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

Cependant, j’ai récemment rencontré un très gros (presque) CSV simple ligne, où le séparateur de lignes n’était en fait pas _'\n'_ mais _'|'_.

  • Lire ligne par ligne n'était pas une option, mais je devais quand même le traiter ligne par ligne.
  • La conversion de _'|'_ en _'\n'_ avant le traitement était également hors de question, car certains des champs de ce csv contenaient _'\n'_ (entrée utilisateur en texte libre).
  • L'utilisation de la bibliothèque csv a également été exclue en raison du fait que, du moins dans les premières versions de la bibliothèque, il est codé en dur pour lire l'entrée ligne par ligne .

Je suis venu avec l'extrait suivant:

_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)
    """
    incomplete_row = None
    while True:
        chunk = f.read(chunksize)
        if not chunk: # End of file
            if incomplete_row is not None:
                yield incomplete_row
                break
        # Split the chunk as long as possible
        while True:
            i = chunk.find(sep)
            if i == -1:
                break
            # If there is an incomplete row waiting to be yielded,
            # prepend it and set it back to None
            if incomplete_row is not None:
                yield incomplete_row + chunk[:i]
                incomplete_row = None
            else:
                yield chunk[:i]
            chunk = chunk[i+1:]
        # If the chunk contained no separator, it needs to be appended to
        # the current incomplete row.
        if incomplete_row is not None:
            incomplete_row += chunk
        else:
            incomplete_row = chunk
_

Je l'ai testé avec succès sur des fichiers volumineux et avec différentes tailles de blocs (j'ai même essayé une taille de bloc de 1 octet, juste pour m'assurer que l'algorithme ne dépend pas de la taille).

22
user48678
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

MISE À JOUR: L’approche est mieux expliquée dans https://stackoverflow.com/a/4566523/38592

9
myroslav

Je pense que nous pouvons écrire comme ceci:

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
TonyCoolZhu

Reportez-vous à la documentation officielle de python https://docs.python.org/zh-cn/3/library/functions.html?#iter

Peut-être que cette méthode est plus pythonique:

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)
1
bruce

Je suis dans une situation quelque peu similaire. Il est difficile de savoir si vous connaissez la taille de bloc en octets; Je ne le fais généralement pas, mais le nombre d'enregistrements (lignes) requis est connu:

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

Mise à jour: Merci nosklo. Voici ce que je voulais dire. Cela fonctionne presque, sauf qu'il perd une ligne "entre" des morceaux.

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

Est-ce que le truc ne perd aucune ligne, mais ça n'a pas l'air très gentil.

1
SilentGhost

je ne suis pas autorisé à commenter en raison de ma faible réputation, mais la solution SilentGhosts devrait être beaucoup plus facile avec file.readlines ([sizehint])

méthodes de fichier python

edit: SilentGhost a raison, mais cela devrait être mieux que:

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

Pour traiter ligne par ligne, c'est une solution élégante:

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

Tant qu'il n'y a pas de lignes vides.

0
crizCraig