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.
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)
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.
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)
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 _'|'
_.
'|'
_ 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).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).
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
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)
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)
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.
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])
edit: SilentGhost a raison, mais cela devrait être mieux que:
s = ""
for i in xrange(100):
s += file.next()
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.