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

j'ai un très gros fichier 4Go et quand j'essaie de le lire, mon ordinateur se bloque. Donc je veux le lire morceau par morceau et après traitement de chaque pièce stocker la pièce traitée dans un autre dossier et lire la pièce suivante.

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

j'aimerais avoir un méthode paresseuse .

223
demandé sur Pratik Deoghare 2009-02-06 12:11:13

11 réponses

pour écrire une fonction paresseuse, il suffit d'utiliser 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 serait d'utiliser iter et une fonction d'aide:

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)
338
répondu nosklo 2009-02-06 09:30:56

si votre ordinateur, OS et python sont 64 bits , alors vous pouvez utiliser le module mmap pour mapper le contenu du fichier en mémoire et y accéder avec des indices 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, OS ou python sont 32 bits , alors mmap-ing grands fichiers peuvent réserver de grandes parties de votre espace d'adresse et starve votre programme de la mémoire.

30
répondu Community 2017-05-23 11:55:04
"151910920 de fichier".readlines () accepte un argument taille optionnel 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)
25
répondu Anshul 2010-01-21 18:27:59

regardez ce post sur Neopythonic : "Trier un million d'entiers 32 bits en 2MB de RAM en utilisant Python"

21
répondu Paolo Tedesco 2009-02-06 09:28:12

il y a déjà beaucoup de bonnes réponses, mais j'ai rencontré un problème similaire récemment et la solution dont j'avais besoin n'est pas listée ici, donc j'ai pensé que je pourrais 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 de fichier lui - même comme générateur paresseux:

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

cependant, j'ai récemment rencontré une très grande (presque) ligne simple csv, où le séparateur de rangée était en fait pas '\n' mais '|' .

  • Lire ligne par ligne n'était pas une option, mais j'avais encore besoin de le traiter ligne par ligne.
  • conversion '|' en '\n' avant traitement était également hors de question, parce que certains des champs de cette csv contenait '\n' (Entrée libre de l'utilisateur de texte).
  • utilisant la bibliothèque csv a également été exclue parce que le fait que, à moins dans les premières versions de la lib, il est difficile de lire la ligne d'entrée par la ligne .

j'ai trouvé 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 de grands fichiers et avec différentes tailles de morceaux (j'ai même essayé un chunksize de 1 octet, juste pour s'assurer que l'algorithme ne dépend pas de la taille).

14
répondu user48678 2017-05-23 12:26:07
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

8
répondu myroslav 2017-05-23 11:47:23

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
répondu TonyCoolZhu 2013-11-06 02:15:10

Je ne suis pas autorisé à commenter en raison de ma mauvaise réputation, mais Solution SilentGhosts devrait être beaucoup plus facile avec le fichier.readlines ([sizehint])

fichier python méthodes

edit: SilentGhost est à droite, mais cela devrait être mieux que:

s = "" 
for i in xrange(100): 
   s += file.next()
1
répondu sinzi 2009-02-06 10:59:24

je suis dans une situation un peu similaire. Il n'est pas clair si vous connaissez la taille des morceaux en octets; Je ne le sais 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. Il fonctionne presque, sauf qu'il perd une ligne entre deux morceaux.

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

fait le tour sans perdre aucune ligne, mais il ne semble pas très agréable.

1
répondu SilentGhost 2011-03-15 05:33:14

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
répondu crizCraig 2012-05-01 23:12:15

vous pouvez utiliser le code suivant.

file_obj = open('big_file') 

ouvrir () retourne un objet de fichier

alors utilisez os.stat pour obtenir la taille

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

for i in range( file_size/1024):
    print file_obj.read(1024)
-1
répondu shrikant 2015-06-18 13:20:52