Méthode Lazy pour lire le fichier Big en Python?
Question
J'ai un très gros fichier de 4 Go et lorsque je tente de lire mon ordinateur se bloque. Je tiens donc à le lire morceau par morceau et après le traitement de chaque pièce stocker la pièce traitée dans un autre fichier et lire morceau suivant.
est-il une méthode pour ces pièces yield
?
J'aimerais avoir une méthode paresseuse .
La solution
Pour écrire une fonction paresseux, 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 et iter
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é en ligne, l'objet de fichier est déjà un générateur paresseux de lignes:
for line in open('really_big_file.dat'):
process_data(line)
Autres conseils
Si votre ordinateur, système d'exploitation et python sont 64 bits , vous pouvez utiliser le module mMAP pour carte le contenu du fichier dans la mémoire et l'accès avec des indices et des tranches. Voici un exemple 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 l'ordinateur, OS ou python sont 32 bits , puis mmap-ing de gros fichiers peuvent réserver une grande partie de l'espace d'adressage et mourir de faim votre programme de mémoire.
file.readlines () prend dans un argument de taille en option qui se rapproche du nombre de lignes lu dans les lignes retourné.
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 je suis tombé sur un problème similaire récemment et la solution que je avais besoin ne figure pas ici, donc je pensais que je pouvais compléter ce fil.
80% du temps, je dois lire des fichiers ligne par ligne. Ensuite, comme l'a suggéré dans cette répondre , vous voulez utiliser l'objet 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é un très très grand (presque) csv ligne unique, où le séparateur de rangée était en fait pas, mais '\n'
'|'
.
- ligne de lecture par la ligne n'a pas été une option, mais je reste nécessaire pour traiter ligne par ligne.
- Conversion à <=> avant le traitement était <=> également de la question, parce que certains des champs de cette csv contenu <=> (entrée utilisateur de texte libre).
- Utilisation de la bibliothèque csv a également été exclu parce que le fait que, au moins dans les premières versions du lib, il est codé en dur à lire la ligne d'entrée 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 de gros fichiers et bloc de taille différentes (j'ai même essayé un chunksize de 1 octet, juste pour vérifier que l'algorithme n'est pas fonction 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é dans https://stackoverflow.com/a/4566523/38592
Je ne suis pas autorisé à commenter en raison de ma faible réputation, mais la solution de SilentGhosts devrait être beaucoup plus facile avec file.readlines ([sizeHint])
edit: SilentGhost est juste, mais cela devrait être mieux que:
s = ""
for i in xrange(100):
s += file.next()
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)
Je suis dans une situation un peu similaire. On ne sait pas si vous connaissez la taille du morceau en octets; Je ne sont généralement pas, mais le nombre d'enregistrements (lignes) qui est nécessaire 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 » morceaux.
chunk = [next(gen) for i in range(lines_required)]
Est-ce que l'affaire w / o perdre toutes les lignes, mais il ne semble pas très agréable.
Reportez-vous à la documentation officielle de python https: // docs .python.org / zh-cn / 3 / bibliothèque / functions.html? NB_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)
à la ligne de processus en ligne, ceci 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 sommes.
vous pouvez utiliser le code suivant.
file_obj = open('big_file')
open () retourne un objet de fichier
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)