Optimisation des chaînes de caractères en Python

Tags associés : , , posté le 21 january 2006


Dans quelles situations utiliser les chaînes de caractère ? Pourquoi pas des listes ? Et les list-comprehension dans tout ça ? Réponses en tests, c'est plein de strings mais ne vous inquiétez pas, rien de sexuel ;)

Considérons une chaîne de caractères assez conséquente :

strings = ["tagada"]*1000000 + ["tsouintsouin"]*1000000

Puis une fonction concaténant tous les tagada :

def foo1():
    string_final = ""
    for string in strings:
        if not "tsouin" in string:
            string_final += string
    return string_final

Nous obtenons une fonction qui met 4,44 secondes pour renvoyer une longue chaîne. Pas vraiment rapide, je suis sûr qu'on pourrait aller plus vite avec des listes :

def foo2():
    string_final = []
    for string in strings:
        if not "tsouin" in string:
            string_final.append(string)
    return "".join(string_final)

Cette fois ce sont 4,65 secondes qui sont nécessaires pour effectuer la même opération... l'ajout d'un élément à une liste est donc très coûteux en temps aussi ! Et c'est loin des « centaines de fois » plus rapide qui peuvent être annoncées. C'est d'ailleurs la raison pour laquelle il existe les comprehension-lists :

def foo3():
    return "".join([string for string in strings if not "tsouin" in string])

Qui ne mettent dans ce cas là que 3,25 secondes ! Voila qui fait réfléchir lors de votre prochaine implémentation ;-). Mais au fait s'il est aussi coûteux en temps d'ajouter des éléments à une liste, pourquoi ne pas créer une chaîne de caractères que l'on découpe ensuite en une liste ?! Farfelu ? Vérifions :

def foo4():
    string_final = ""
    for string in strings:
        if not "tsouin" in string:
            string_final += string + "separateur"
    return string_final.split("separateur")

def foo5():
    string_final = []
    for string in strings:
        if not "tsouin" in string:
            string_final.append(string)
    return string_final

La première fonction renvoie une liste en 17,8 secondes, la seconde en 4,31 secondes, en effet c'était farfelu. Et avec une list-comprehension ? Théoriquement encore plus rapide que précédemment puisque qu'il y a un join() en moins, je vous laisse vérifier.

Conclusion : utilisez les list-comprehension au maximum lorsque c'est possible quitte à faire des [foo(string) for string in strings] s'il y a de gros post-traitements de string. Quant à l'utilisation de concaténation de chaînes de caractères contre l'ajout à une liste, le temps où les listes étaient beaucoup plus rapides est apparemment révolu, choisissez donc en fonction du type de données et non de la rapidité...

[Edit/Erratum] : un billet suivant explique une nouvelle méthode d'utilisation des listes plus rapide que les chaînes de caractères.

Je vous rappelle qu'un billet récapitule l'ensemble des bonnes pratiques et optimisations en Python.


Billets contextuels



Bonnes pratiques et astuces Python

Image associée au billet

Ça faisait un moment que je n'avais pas parlé des bonnes pratiques Python mais l'approche de Pycon fr (où je présenterai Django : le pourquoi et le comment le 18 mai), l'événement Python incontournable avec un programme des ...

Benchmarks map, filter vs. list-comprehensions

Image associée au billet

Je viens de tomber sur les snyppets de Seb Sauvage (site que j'apprécie beaucoup par ailleurs) et il y a une phrase qui m'a interpellé sur le paragraphe consacré à zip, map, filter et aux list-comprehensions : Except that ...

Python : lisibilité vs simplicité

Image associée au billet

Le programmeur est fainéant. C'est ainsi. S'il ne l'était pas, il n'essayerait pas de s'aider d'un ordinateur. Du coup il évite dans la mesure du possible de s'encombrer de variables trop longues, ou ...




Ajouter un commentaire

© 2004-2008 David Larlet - Licence (presque) libre - Site enfin propulsé par Django et hébergé par Typhon.