web-dev-qa-db-fra.com

Python writelines () et write () énorme décalage horaire

Je travaillais sur un script qui lisait un dossier de fichiers (chacun d'une taille allant de 20 Mo à 100 Mo), modifiait certaines données dans chaque ligne et réécrivait sur une copie du fichier.

with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.writelines('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')

En exécutant ce code avec un fichier de 90 Mo (~ 900 000 lignes), il a imprimé 140 secondes comme le temps nécessaire pour écrire dans le fichier. Ici, j'ai utilisé writelines(). J'ai donc cherché différentes façons d'améliorer la vitesse d'écriture des fichiers, et dans la plupart des articles que j'ai lus, il était dit que write() et writelines() ne devrait pas montrer de différence puisque j'écris un seul chaîne concaténée. J'ai également vérifié le temps pris pour seulement la déclaration suivante:

new_string = '\n'.join(new_my_list) + '\n'

Et cela n'a pris que 0,4 seconde, donc le temps nécessaire n'était pas dû à la création de la liste. Juste pour essayer write() J'ai essayé ce code:

with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.write('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')

Et il a imprimé 2,5 secondes. Pourquoi y a-t-il une si grande différence dans le temps d'écriture du fichier pour write() et writelines() même s'il s'agit des mêmes données? Est-ce un comportement normal ou y a-t-il quelque chose qui ne va pas dans mon code? Le fichier de sortie semble être le même pour les deux cas, donc je sais qu'il n'y a pas de perte de données.

29
Arjun Balgovind

file.writelines() attend un itérable de chaînes. Il procède ensuite à la boucle et appelle file.write() pour chaque chaîne de l'itérable. En Python, la méthode fait ceci:

def writelines(self, lines)
    for line in lines:
        self.write(line)

Vous passez dans une seule grande chaîne, et une chaîne est également un itérable de chaînes. Lors de l'itération, vous obtenez caractères individuels, des chaînes de longueur 1. Donc, en fait, vous effectuez len(data) des appels séparés à file.write(). Et c'est lent, car vous créez un tampon d'écriture un seul caractère à la fois.

Ne passez pas une seule chaîne à file.writelines(). Passez plutôt une liste ou un tuple ou un autre itérable.

Vous pouvez envoyer des lignes individuelles avec une nouvelle ligne ajoutée dans une expression de générateur, par exemple:

 myWrite.writelines(line + '\n' for line in new_my_list)

Maintenant, si vous pouviez faire clean_data() a générateur, produisant des lignes nettoyées, vous pourriez diffuser des données à partir du fichier d'entrée, via votre générateur de nettoyage de données, et vers le fichier de sortie sans utiliser plus de mémoire que nécessaire pour les tampons de lecture et d'écriture et quel que soit l'état requis pour nettoyer vos lignes:

with open(inputPath, 'r+') as myRead, open(outPath, 'w+') as myWrite:
    myWrite.writelines(line + '\n' for line in clean_data(myRead))

De plus, j'envisagerais de mettre à jour clean_data() pour émettre des lignes avec des retours à la ligne inclus.

43
Martijn Pieters

en complément de la réponse de Martijn, le meilleur moyen serait d'éviter de construire la liste en utilisant join en premier lieu

Passez simplement une compréhension du générateur à writelines, en ajoutant la nouvelle ligne à la fin: pas d'allocation de mémoire inutile et pas de boucle (en plus de la compréhension)

myWrite.writelines("{}\n".format(x) for x in my_list)
6

La méthode 'write (arg)' attend une chaîne comme argument. Donc, une fois qu'il appelle, il écrit directement. c'est la raison pour laquelle il est beaucoup plus rapide. où comme si vous utilisez la méthode writelines(), il attend la liste de chaînes comme itérateur. donc même si vous envoyez des données à writelines, il suppose qu'il a un itérateur et essaie de le parcourir. comme il s'agit d'un itérateur, il faudra un certain temps pour le répéter et l'écrire.

Est-ce clair ?

2
nanithehaddock