Python writelines () et write () énorme différence de temps

Je travaillais sur un script qui lisait un dossier de fichiers (chacun de taille allant de 20 Mo à 100 Mo), modifiait certaines données dans chaque ligne et réécrivait 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 write() et writelines() ne devraient pas en montrer différence puisque j'écris une seule chaîne concaténée. J'ai également vérifié le temps pris pour seulement ce qui suit la déclaration:

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

Et il n'a fallu que 0,4 seconde, donc le grand temps pris n'était pas à cause de 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 si ce sont les mêmes données? Est-ce un comportement normal ou y a-t-il quelque chose qui ne va pas 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.

24
demandé sur Arjun Balgovind 2017-06-15 09:56:05

3 réponses

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

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

Vous passez une seule grande chaîne, et une chaîne est une itérable de chaînes aussi. Lors de l'itération, vous obtenez caractères individuels, chaînes de longueur 1. Donc, en effet, vous faites len(data) des appels séparés à file.write(). Et c'est lent, parce que vous construisez un tampon d'écriture d'un seul caractère à un temps.

Ne passez pas une seule chaîne à file.writelines(). Passez dans une liste ou un tuple ou un autre itérable à la place.

Vous pouvez envoyer des lignes individuelles avec un retour à la ligne ajouté 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() un 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 la lecture et l'écriture tampons et quel que soit l'état nécessaire 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))

En outre, j'envisagerais de mettre à jour clean_data() pour émettre des lignes avec des nouvelles lignes incluses.

38
répondu Martijn Pieters 2017-06-15 20:06:19

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

Il suffit de passer 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)
5
répondu Jean-François Fabre 2017-06-15 07:06:52

La méthode 'write (arg)' attend string 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 une liste de chaîne comme itérateur. donc, même si vous envoyez des données à writelines, Il suppose qu'il a un itérateur et qu'il essaie d'itérer dessus. donc, puisque c'est un itérateur il faudra un certain temps pour parcourir et de les écrire.

Est-ce clair ?

2
répondu nanithehaddock 2017-06-15 07:03:12