1
votes

Comment traiter un très gros fichier en python?

Je traite un très gros fichier contenant du texte brut en python. Le contenu du fichier a le format suivant:

  • chaque enregistrement est séparé par point_separator
  • chaque champ est séparé par field_separator .
with open(file_name, mode='rt', encoding='utf-8') as reader:
    text = reader.read()
    objects = text.strip().split('point_separator')
    ......

Auparavant, je lisais le fichier complet en utilisant l'appel d'API d'ouverture de fichier python:

new record
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
call
point_separator
new record
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
call
point_separator
new record
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
another field
field_separator
call
point_separator

Cependant, cela échoue avec MemoryError lorsque le fichier est très volumineux, c'est-à-dire 20 Go et que je traite dans une machine contenant 16 Go RAM.

Le problème est que je ne peux pas lire cette ligne de fichier par ligne car je dois collecter tous les champs basés sur field_separator jusqu'à ce que je vois un point_separator .

Y a-t-il un moyen pour que le système d'exploitation utilise la pagination et il serait traité de manière transparente?


4 commentaires

Est-il possible d'analyser le fichier ligne par ligne (par exemple cmdlinetips.com/2011/08/... )? Dans quelle mesure est-il important de lire plus d'un seul morceau du fichier à la fois?


Le problème est que je ne peux pas lire ce fichier ligne par ligne car je dois collecter tous les champs basés sur field_separator jusqu'à ce que je vois un point_separator . Et certains champs peuvent avoir plusieurs lignes.


Il peut toujours être possible de le gérer ligne par ligne, en gardant manuellement une trace de la dernière fois que vous avez vu un point_separator ou un field_separator pour déterminer comment gérer la ligne actuelle que vous sur, mais je vois le problème.


Lisez jusqu'au point_separator , en collectant les champs jusqu'à ce que vous y arriviez. Traitez ensuite l'enregistrement. Répéter. N'essayez pas de rassembler tous les enregistrements en mémoire à la fois.


3 Réponses :


0
votes

traiter ligne par ligne

objects = []
fields = []
field = ''
with open(file_name, mode='rt', encoding='utf-8') as reader:
    for line in reader:
        line = line.strip()
        if 'point_separator' == line:
            objects.append(fields)
            fields = []
        elif 'field_separator' == line:
            fields.append(field)
            field = ''
        else:
            field += line + '\n'


0 commentaires

1
votes

Vous pouvez écrire votre propre fonction de générateur qui vous permet d'itérer sur le classer un enregistrement à la fois, sans jamais lire le fichier entier en mémoire simultanément. Par exemple:

['new record\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'call\n']
['new record\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'call\n']
['new record\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'another field\n', 'call\n']

Avec l'exemple de version dans la question, cela donne:

def myiter(filename, point_separator):
    with open(filename, mode='rt', encoding='utf-8') as reader:
        text = ''
        while True:
            line = reader.readline()
            if not line:
                break
            if line.strip() == point_separator:
                yield text
                text = ''
            else:
                text += line
    if text:
        yield text


# put in the actual separator values here - tested the version shown 
# in the question using literal "point_separator" and "field_separator" 
point_separator = 'point_separator' 
field_separator = 'field_separator' 
filename = 'test.txt'

for record in myiter(filename, point_separator):
    fields = record.split(field_separator + '\n')
    print(fields)

Vous pouvez ensuite supprimer les nouvelles lignes selon vos besoins ( Je ne l'ai pas déjà fait pour vous, car je ne sais pas si les champs peuvent être multilignes.)

De plus, je n'ai rien fait de spécial avec le "nouvel enregistrement" et " appeler". Vous pouvez faire print (fields [1: -1]) pour les exclure.


0 commentaires

1
votes

Vous pouvez utiliser itertools.groupby pour itérer d'abord par tout ce qui se trouve entre les lignes "nouvel enregistrement", puis en interne par tout ce qui se trouve entre les lignes "field_separator". Dans le groupby externe, new_record sera vrai pour toutes les lignes contenant le texte "nouvel enregistrement" et new_record devient d'abord faux, vous savez que vous êtes dans l'enregistrement et faites l'intérieur groupby.

import itertools

def record_sep(line):
    return line.strip() == "new record"
    
def field_sep(line):
    return line.strip() == "field_separator"
    
records = []

with open('thefile') as fileobj:
    for new_record, record_iter in itertools.groupby(fileobj, record_sep):
        # skip new record group and proceed to field input
        if not new_record:
            record = []
            for new_field, field_iter in itertools.groupby(record_iter, field_sep):
                 # skip field separator group and proceed to value group
                if not new_field:
                    record.append(list(field_iter)) # assuming multiple values in field
            records.append(record)


for record in records:
    print(record)


0 commentaires