Lorsque vous programmez en Python, il est crucial de comprendre les performances de votre code et les opérations qui peuvent être optimisées pour réduire le temps d'exécution.
Dans cet article, nous explorerons quelques-unes des optimisations courantes que vous pouvez apporter à votre code Python pour le rendre plus rapide et plus efficace.
Notion de complexité algorithmique
O(n) ou encore O(1), vous avez déjà vu ces termes sans savoir à quoi ils correspondent, n'est-ce pas ?
Avant de plonger dans les optimisations, il est important de comprendre la notation de complexité algorithmique.
La notation O(n) (ou complexité linéaire) indique que le temps d'exécution d'une opération augmente proportionnellement à la taille de l'entrée. Par exemple, parcourir une liste de n éléments aura une complexité O(n).
L'exemple le plus courant et évident est le parcours d'un itérable (une liste par exemple) avec une boucle for.
Cette opération est d'une complexité O(n) car il sera plus rapide de parcourir une liste de 10 éléments qu'une liste de 50,000 éléments.
En revanche, la notation O(1) (ou complexité polynomiale) signifie que le temps d'exécution de l'opération reste constant quelle que soit la taille de l'entrée. Un exemple courant est l'accès à un élément dans un dictionnaire Python, qui a une complexité O(1).
Ainsi, le temps d'exécution sera le même pour accéder à un élément spécifique du dictionnaire, que celui-ci contienne 1 entrée ou 10 millions.
Optimisations courantes
Il y a un certain nombre d'opérations de base en Python qu'il est très facile d'optimiser, découvrons-les ensemble.
Enfin, vous pouvez aussi retrouver la notation O(log (n)) qui signifie un complexité logarithmique.
Parcours de Liste
Lorsque vous devez vérifier si un élément existe dans une liste en la parcourant, utilisez un ensemble (set) si possible, car ces derniers permettent des recherches en O(1) comparé aux listes en O(n).
Sinon, vous pouvez utiliser les compréhensions de liste qui sont généralements plus rapides que les boucles for traditionnelles pour créer des listes car elles permettent de créer des listes de manière concise et efficace.
# Recherche dans une liste (O(n)) ma_liste = [1, 2, 3, 4, 5] if 3 in ma_liste: print("Trouvé") # Recherche dans un set (O(1)) mon_set = {1, 2, 3, 4, 5} if 3 in mon_set: print("Trouvé") # Utilisation de boucle for traditionnelle (O(n)) ma_liste = [] for i in range(10): ma_liste.append(i * 2) # Utilisation de compréhension de liste (O(n)) ma_liste = [i * 2 for i in range(10)]
Opérations sur les listes
Certaines opérations sur les listes peuvent être coûteuses en temps, notamment l'utilisation de la méthode pop() pour supprimer le premier élément d'une liste avec pop(0) qui a une complexité O(n).
Si vous souhaitez utiliser pop, ne l'utilisez que de manière classique avec pop() pour supprimer le dernier élément, cette opération ayant une complexité O(1).
Le problème de pop quand il est utilisé pour enlever un autre élément que le dernier est que Python va devoir décaler tous les éléments suivants dans la liste. Si votre liste contient beaucoup d'éléments, l'opération sera donc coûteuse et d'une complexité O(n).
Si vous souhaitez vraiment supprimer le premier élément d'une liste, privilégiez l'utilisation du conteneur deque du module collections qui vous donne l'accès à la méthode popleft() vous permettant de supprimer la valeur la plus à gauche d'une liste, donc la première, avec une complexité O(1) !
# Moins efficace (O(n)) ma_liste = [1, 2, 3, 4, 5] premier_item = ma_liste.pop(0) # Plus efficace (O(1)) ma_liste = [1, 2, 3, 4, 5] dernier_item = ma_liste.pop() # Ou en utilisant le conteneur deque du module collections from collections import deque mon_deque = deque([1, 2, 3, 4, 5]) dernier_item_deque = mon_deque.popleft()
Le tri d'une liste
La méthode sort() pour trier une liste a une complexité moyenne de O(n log(n)) ce qui est particulièrement élevé.
Pour de grandes listes, envisagez plutôt d'utiliser sorted() qui crée une nouvelle liste triée sans modifier l'original.
Vous ne gagnerez pas en complexité de temps (car il n'est techniquement pas possible d'améliorer la complexité d'un triage en Python) mais vous gagnerez en complexité d'espace.
# Trier la liste en place O(n log n) ma_liste = [5, 3, 1, 4, 2] ma_liste.sort() # Créer une nouvelle liste triée O(n log n), même complexité de temps mais moins d'espace de mémoire utilisé! ma_liste = [5, 3, 1, 4, 2] liste_triee = sorted(ma_liste)
Dans ce cas-ci, le montant de mémoire requis par le programme sera moins elevé et c'est aussi une forme d'optimisation !
L'accès aux données
Les ensembles set et les dictionnaires dict offrent des temps d'accès rapides grâce à l'utilisation de tables de hachage.
Ils sont à privilégier lorsque vous devez effectuer des recherches rapides dans un ensemble de données.
# Utilisation de set pour une recherche rapide (O(1)) mon_set = {1, 2, 3, 4, 5} if 3 in mon_set: print("Trouvé") # Utilisation de dict pour un accès rapide (O(1)) mon_dict = {"a": 1, "b": 2, "c": 3} ma_valeur_b = mon_dict.get("b")
Conclusion
En optimisant votre code Python, vous pouvez améliorer considérablement ses performances et sa réactivité.
En gardant à l'esprit les complexités algorithmiques des opérations courantes et en utilisant des structures de données efficaces, vous pouvez réduire le temps d'exécution de votre code.
N'oubliez pas de mesurer les performances de votre code à l'aide d'outils comme timeit pour évaluer l'impact de vos optimisations !