title: Optimisation des chaînes de caractères en Python slug: optimisation-des-chaines-de-caracteres-en-python date: 2006-01-21 20:13:01 type: post vignette: images/logos/bonnes_pratiques_python.png contextual_title1: Bonnes pratiques et astuces Python contextual_url1: 20080511-bonnes-pratiques-et-astuces-python contextual_title2: Benchmarks map, filter vs. list-comprehensions contextual_url2: 20061025-benchmarks-map-filter-vs-list-comprehensions contextual_title3: Python : lisibilité vs simplicité contextual_url3: 20060425-python-et-underscore

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.