Formater les strings en Python

Formater les chaînes de caractères de sorte à ce qu'elles utilisent des variables est la base de l'affichage d'un script. Python offre une multitude de méthodes pour y parvenir. Voici donc un article retraçant les différentes méthodes avec leurs avantages et leur inconvénients. 

 

Pour les mecs trop rapide  

var1 = 5
var2 = 8
new_str = f"La somme entre {var1} et {var2} est {var1 + var2}"
print(new_str)
# La somme entre 5 et 8 est 13

 

La concaténation

La méthode ancestrale pour formater du texte. Elle consiste à utiliser l'opérateur + entre deux autres chaînes de caractères, cela permettra de former une nouvelle chaîne constituée des deux sous-chaînes mise l'une à la suite de l'autre.

str1 = "Le monde de la banque"
str2 = " est intéressant"
new_str = str1 + str2
print(new_str)
# Le monde de la banque est intéressant

 

Problème 

Cette méthode fonctionne très bien, mais deux grands problèmes se posent à nous.

Le 1er est que, former une chaîne de caractères avec de nombreuse variables rend rapidement le code illisible. En effet, la succession de variables va former une ligne de plus en plus grande et très peut compréhensible, de plus chaque chaîne de caractères ou variables à mettre en forme, oblige l'utilisation du  + de manière systématique entre les objets, ce qui alourdit une fois de plus la relecture du script.

Le second point est plus problématique, l'utilisation de l'opérateur + pour formater des chaînes de caractères suppose que tous les objets à formater sont des instances de string < 'str' >, sous peine de lever une exception.

var1 = 5
var2 = 8
new_str = "La somme entre " + var1 + " et " + var2 + " est " + var1 + var2
print(new_str)

# Traceback (most recent call last):
#   File "/my/path/cryptide.py", line 3, in <module>
#     new_str = "La somme entre " + var1 + " et " + var2 + " est " + var1 + var2
# TypeError: must be str, not int

Ce comportement oblige donc, soit à caster (Transtyper en français, c'est le fait d'utiliser des fonctions comme str(), int(), ... pour forcer et changer le type de la variable passée en paramètre) toutes les variables à formater ce qui alourdit encore le code, soit de connaître et vérifier les types des objets avant formatage. Ce dernier point peut être un inconvénient comme un avantage.

Performance

Pour ce qui des performances de cette méthode, voici un exemple de script pour estimer le temps d'exécution des instructions :

from timeit import timeit

tester = """
str1 = "Le monde de la banque"
str2 = " est intéressant"
new_str = str1 + str2
"""

time = timeit(tester, number=10 ** 7)

print(time) 
# 0.8245257418602705

Ce test exécute la chaîne de caractères tester comme instruction Python autant de fois que l'indique la variable number, ici 107, soit 10000000 fois. Une fois les exécutions finies, il affiche en seconde le temps pris pour les effectuer, ici 0.824 seconde.
La méthode de concaténation de deux chaînes de caractères a donc pris moins d'une seconde pour faire les 107 formatages.
Dans la suite de cet article, je réutiliserai cette méthode pour évaluer et comparer les différentes méthodes de formatage et leurs temps d'exécution.

 

Les emprunts du C

Le C a apporté beaucoup à la programmation, et donc à Python  cool. Parmi ces apports, il y a l'opérateur % permettant de ... formater des strings, oui ! Voici tout de suite un exemple d'utilisation :

site = "cryptide"
phrase = "Le site %s parle de Python" % site
print(phrase)
# Le site cryptide parle de Python

Il suffit donc de mettre %s à l'endroit où l'on veut insérer la variable, et de spécifier cette dernière à la fin en la préfixant de %. Cette méthode ne limite pas l'utilisation à une seule variable, il est possible d'en mettre plusieurs d'affilées : 

animal = "chien"
race = "teckel"
phrase = "J'ai un %s, c'est un %s" % (animal, race)
print(phrase)
# J'ai un chien, c'est un teckel

 

Pourquoi il y a un "s" dans le pourcentage ?

Si vous avez bien suivi, cette question devrait vous brûler les lèvres ! En effet, il y a un "s" après le pourcentage et il n'est pas là pour rien, il sert a définir le type de la variable à formater, le "s" correspond à string. Mais il en existe d'autres, par exemple %d avec d pour décimal :

age = 11
phrase = "Mon chien a %d ans" % age
print(phrase)
# Mon chien a 11 ans

On remarque ici que age est du type < 'int' > et que le formatage a fonctionné sans lever d'exception. Mais attention si la variable avait été un string l'utilisation du %d aurai levé une exception de TypeError :

age = "11" # <- String
phrase = "Mon chien a %d ans" % age
print(phrase)

# Traceback (most recent call last):
#   File "/my/path/cryptide.py", line 2, in <module>
#     phrase = "Mon chien a %d ans" % age
# TypeError: %d format: a number is required, not str

Il existe encore d'autres suffixes, voici un tableau récapitulatif tout droit venu de la documentation officielle de Python :

 

Type

Signification

'b'

Format binaire. Affiche le nombre en base 2.

'c'

Caractère. Convertit l’entier en le caractère Unicode associé avant de l’afficher.

'd'

Entier décimal. Affiche le nombre en base 10.

'o'

Format octal. Affiche le nombre en base 8.

'x'

Format hexadécimal. Affiche le nombre en base 16 en utilisant les lettres minuscules pour les chiffres au-dessus de 9.

'X'

Format hexadécimal. Affiche le nombre en base 16 en utilisant les lettres majuscules pour les chiffres au-dessus de 9.

'n'

Nombre. Pareil que 'd' si ce n’est que l’environnement linguistique est utilisé afin de déterminer le séparateur de nombres approprié.

None

Pareil que 'd'.

En plus des types de représentation ci-dessus, les entiers peuvent aussi être formatés avec les types de représentation des flottants listés ci-dessous (à l’exception de 'n' et None). Dans ce cas, la fonction float() est utilisée pour convertir l’entier en flottant avant le formatage.

Les types de représentation pour les nombres flottants et les valeurs décimales sont :

Type

Signification

'e'

Notation par exposant. Affiche le nombre dans sa notation scientifique en utilisant la lettre “e” pour indiquer l’exposant. La précision par défaut est 6.

'E'

Notation par exposant. Pareil que 'e' sauf l’utilisation de la lettre majuscule “E” comme séparateur.

'f'

Virgule fixe. Affiche le nombre comme un nombre à virgule fixe. La précision par défaut est 6.

'F'

Virgule fixe. Pareil que 'f' à part nan qui devient NAN et inf qui devient INF.

'g'

Format général. Pour une précision donnée p >= 1, ceci arrondit le nombre à p chiffres significatifs et puis formate le résultat soit en virgule fixe soit en notation scientifique, en fonction de la magnitude.

Les règles précises sont les suivantes : supposons que le résultat formaté avec le type de représentation 'e' et une précision 1 ait un exposant exp. Alors, si -4 <= exp <= p, le nombre est formaté avec le type de représentation 'f' et une précision p-1-exp. Sinon, le nombre est formaté avec le type de représentation 'e' et une précision p-1. Dans les deux cas, les zéros finaux et non significatifs sont retirés, et la virgule est également retirée s’il n’y a aucun chiffre la suivant.

Les valeurs suivantes : infini négatif, infini positif, zéro positif, zéro négatif, not a number sont formatées respectivement par inf, -inf, 0, -0 et nan, peu importe la précision.

Une précision de 0 est interprétée comme une précision de 1. La précision par défaut est 6.

'G'

Format général. Pareil que 'G' si ce n’est que 'E' est utilisé si le nombre est trop grand. Également, la représentation des infinis et de Nan sont en majuscules également.

'n'

Nombre. Pareil que 'g', si ce n’est que l’environnement linguistique est pris en compte pour insérer le séparateur approprié.

'%'

Pourcentage. Multiplie le nombre par 100 et l’affiche en virgule fixe ('f'), suivi d’un symbole pourcent '%'.

None

Pareil que 'g', si ce n’est que lorsque la notation en virgule fixe est utilisée, il y a toujours au moins un chiffre derrière la virgule. La précision par défaut celle nécessaire pour afficher la valeur donnée. L’effet visé est de le faire correspondre à la valeur renvoyée par str() altérée par les autres modificateurs de format.

 

Performance

Pour ce qui est de la performance, même technique que précédemment. Et on remarque que l'on a besoin cette fois-ci de plus de 2 secondes pour faire le formatage avec cette méthode et avec le même nombre de d'exécutions.

from timeit import timeit

tester = """
str1 = "est intéressant"
new_str = "Le monde de la banque %s" % str1
"""
time = timeit(tester, number=10 ** 7)
print(time)
# 2.4964270517230034

 

Problème

Comme on l'a vu, cette méthode permet de palier ces problèmes de typage, mais pas totalement. De plus, comme pour la concaténation, l'utilisation de cette dernière, rend le formatage très difficile à lire et très lourd, on ne sait pas par quelle valeur %s va être remplacée avant d'avoir fini de lire la ligne.
Tous ces inconvenants et le fait que les performances ne soient pas au rendez-vous font que cette méthode est dépréciée et qu'elle ne devrai plus être utilisée, mais dans la réalité beaucoup de personnes l'utilisent encore et donc il est important de la connaître.

 

Format

Pour palier ces problèmes énoncés ci-dessus, Python a essayé de proposer une solution dans sa version 2.6, par le biais de sa fonction format(). Voici son utilisation :

couleur = 'noir'
phrase = "Mon teckel est tout {}".format(couleur)
print(phrase)
# Mon teckel est tout noir

Un peu comme dans la méthode précédente, il sera nécessaire de mettre des accolades (bracket en anglais), permettant de localiser l'endroit du changement, et de spécifier la variable associée dans la méthode format(). L'avantage ici, c'est que peu importe le type de la variable, elle sera automatiquement mise au format string, avant d'être ajoutée à la chaîne de caractères. Un autre point positif, c'est qu'il est possible d'associer des mots-clés entre accolades rendant le texte à formater plus lisible :

couleur = 'noir'
phrase = "Mon teckel est tout {couleur}".format(couleur=couleur)
print(phrase)
# Mon teckel est tout noir

Tout comme précédemment, il est possible d'enchaîner les variables.

nouriture1 = 'les croquettes'
nouriture2 = 'la paté'
phrase = "Il adore {first} et {seconde}".format(first=nouriture2, seconde=nouriture1)
print(phrase)
# Il adore la paté et les croquettes

Il est même possible d'utiliser une même variable plusieurs fois, mais avec des mots-clés différents :

race = 'teckel'
phrase = "Mon {race} se nomme {prenom}".format(race=race, prenom=race)
print(phrase)
# Mon teckel se nomme teckel

La fonction format() offre une autre possibilité, c'est d'utiliser une variable en fonction de son positionnement dans la méthode format(), il suffira juste de mettre entre accolades, non pas un mot-clé, mais une position comme le montre l'exemple ci-dessous :

lieux = 'Strasbourg'
association = 'SPA'
phrase = "Il vient de la {1} de {0}".format(lieux, association)
print(phrase)
# Il vient de la SPA de Strasbourg

 

Performance

Pour ce qui est des performances, ce n'est toujours pas ça, cette fois-ci, on dépasse les 3 secondes d'exécution ...

from timeit import timeit

tester = """
str1 = "est intéressant"
new_str = "Le monde de la banque {}".format(str1)
"""
time = timeit(tester, number=10 ** 7)
print(time)
# 3.570725478231907

 

Problème

Beaucoup des problèmes vus précédemment ont été résolus avec cette méthode. Mais certains résistent encore. En effet, si l'on veut formater beaucoup de variables, nous allons nous retrouver avec une méthode format() avec beaucoup de variables qui encombrent la ligne et donc la lisibilité. Et de plus, malgré tous les avantages que la méthode apporte, le plus gros inconvénient, c'est son temps d'exécution qui est plus long que toutes les autres méthodes vues précédemment.

Tant d'avantages, mais encore des inconvénients, qu'allons nous faire ? Python, a-t-il une autre solution ? La réponse est bien sûr que oui !

 

Les F-string

Nous y voilà, le dernier bébé de Python arrivé en version 3.6, les F-strings. Voilà quoi ça ressemble :

nb_familles = 3
phrase = f"Teckel a connu {nb_familles} familles avant d'arriver chez cryptide"
print(phrase)
# Teckel a connu 3 familles avant d'arriver chez cryptide

Comme vous pouvez le voir, il suffit de préfixer le string d'un f et d'appeler la variable à mettre en forme directement dans un jeu d'accolades. La f-string va directement aller chercher la variable correspondante et la formater dans la chaîne de caractères et ça sans problème de type. Il est tout-à fait possible enchaîner les variables avec la même logique :

race = 'teckel'
lang = 'python'
phrase = f"Ce {race} n'est pas n'importe quel {race} ! Il sait lire le {lang}"
print(phrase)
# Ce teckel n'est pas n'importe quel teckel ! Il sait lire le python

 

Les petits plus 

Toute cette section s'applique aussi au formatage avec la méthode format() vu plus haut.


Les fonctions 

Dans ce formatage, il est possible d'appeler directement des méthodes ou des fonctions qui interagiront directement avec la variable :

race = 'teckel'
phrase = f"C'est même {race.capitalize()}, qui a codé les f-string il y a {25-12} ans !"
print(phrase)
# C'est même Teckel, qui a codé les f-string il y a 13 ans !

 

Pre-formatage

Certains objets de Python ont une implémentation du constructeur __format__()  permettant de donner des paramètres directement dans les accolades de la f-string. Pour cela, il suffit de mettre ':' puis les paramètres voulus de formatage :

nb = 42
phrase = f"Teckel peux même changer la base des nombres int: {nb:d};  hex: {nb:x};  oct: {nb:o};  bin: {nb:b}"
print(phrase)
# Teckel peux même changer la base des nombres int: 42;  hex: 2a;  oct: 52;  bin: 101010

# ou avec les préfixes
phrase = f"Il met même les préfix -> int: {nb:d};  hex: {nb:#x};  oct: {nb:#o};  bin: {nb:#b}"
print(phrase)
# Il met même les préfix -> int: 42;  hex: 0x2a;  oct: 0o52;  bin: 0b101010

Cette implémentation est très pratique pour formater les données lourdes ou les données ayant une représentation différentes entre les pays. Je vais prendre ici l'exemple des dates. Un import très puissant natif à Python pour gérer les dates est le module datetime. Les objets retournés par ce module ont la particularité d'avoir une méthode de pré-formatage puissante directement  implémentée. Cette méthode permet entre autres de choisir l'ordre des éléments temporels, dans l'exemple ci-dessous jour/mois/année :

import datetime

date = datetime.datetime(2021, 9, 8, 15, 38, 58)
phrase = f"Aujourd'hui le {date:%d/%m/%Y}, il m'a appris un truc"
print(phrase)
# Aujourd'hui le 08/09/2021, il m'a appris un truc

Mais si aucun paramètre n'a été donné, un formatage par défaut s'applique :

import datetime

date = datetime.datetime(2021, 9, 8, 15, 38, 58)
phrase = f"Aujourd'hui le {date}, c'est 22e jour du mois de fructidor dans le calendrier républicain français"
print(phrase)
# Aujourd'hui le 2021-09-08 15:38:58, c'est 22e jour du mois de fructidor dans le calendrier républicain français

Il existe énormément de paramètres pour répondre à vos besoins, je vous laisse voir la documentation officielle qui répondra, j'en suis sûr à vos interrogations.

 

Performances

Et alors ce nouveau bébé a-t-il de bonnes performances ?

from timeit import timeit

tester = """
str1 = "est intéressant"
new_str = f"Le monde de la banque {str1}"
"""
time = timeit(tester, number=10 ** 7)
print(time)
# 0.7923058718442917

Moins d'une seconde, pour exécuter les instructions 10 000 000 fois. C'est le meilleur temps (au moment où ces lignes, ont été écrites).

 

Conclusion

Il existe donc plusieurs méthodes de formatage, avec des performances qui varient très fortement. Mais le paramètre performance n'est pas la chose la plus importante à prendre en compte. En effet, les performances calculées ici, sont basées sur la puissance de ma machine, et beaucoup de paramètres auraient pu faire varier les résultats, comme le fait de demander de transtyper des variables dans le formatage (ce qui n'a jamais été fait ici, il y avait uniquement du formatage entre strings).
Quelle méthode choisir alors ? C'est une très bonne question. Tout dépend de vos besoins et de votre environnement de travail. Même si l'utilisation des F-strings semble très attrayante, elles ont la problématique d'être trop récentes. En effet, la transition entre Python2 et Python3 a été difficile (et dans certains cas pas finis), il en résulte que beaucoup de machines sont encore en version 2. Et, si par miracle la possibilité d'utiliser la version 3 est possible, il faut encore qu'elle soit au minimum dans la version 3.6, ce qui n'est pas toujours le cas, surtout dans le monde professionnel où les mises à jour se font rarement sans douleur.

 

le 26 nov. 2024 07:57

Ajouter un commentaire

Vous devez avoir un compte actif pour laisser des commentaires. Veuillez vous connecter, ou vous creer un compte.

0 Commentaire

Il n'y a pas encore de commentaire. Sois le premier à en déposer un !