Les f-chaînes, la méthode format¶
L’essentiel¶
La problématique des chaînes formatées¶
Le formatage de chaînes est une possibilité offerte par de nombreux langages de programmation de créer une chaîne de caractères (comprendre du texte) à partir d’un modèle prédéfini. Typiquement, on peut utiliser le formatage de chaîne pour générer une date en français puisqu’une telle date a la structure jj/mm/aaaa
connue à l’avance ou encore l’heure qui a le format hh:mm:ss
. Mais, au-delà de ces formats figés, de nombreuses compositions structurées comme un sudoku à remplir, une table de conjugaison ou un calendrier :
Janvier 2038
di lu ma me je ve sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
sont avantageusement construites en utilisant des chaînes formatées.
La notion de chaîne formatée¶
Supposons qu’on veuille générer de façon automatisée des phrases ayant la structure suivante :
Son nom est titi et il a 8 ans.
Son nom est Arthur et il a 30 ans.
Son nom est Alfred et il a 100 ans.
D’une phrase à l’autre, seuls le nom et le nombre représentant l’âge changent. Les chaînes ci-dessus sont dites des chaînes formatées : toutes les chaînes ont même structure, ici de la forme
Son nom est XXX et il a YYY ans.
Seuls varient le nom XXX
et l’âge YYY
.
Dans le présent document j’appelerai la chaîne-modèle telle que
Son nom est XXX et il a YYY ans.
un gabarit ou encore un modèle ou parfois une chaîne de formatage. Les parties à remplacer sont appelées champs de remplacement (en anglais remplacement field). Un gabarit est le modèle à partir duquel d’autres chaînes vont être produites par substitution.
Panorama des chaînes formatées en Python¶
Python dispose de trois syntaxes pour construire des chaînes formatées :
- les f-chaînes,
- les chaînes formatées avec la méthode
format
, - les chaînes formatées classiques.
Pour des situations simples et dans de nombreuses situations courantes, les f-chaînes fournissent la méthode la plus souple et la plus lisible. Elles n’existent que depuis la version 3.6 de Python.
Les f-chaînes sont une déclinaison des chaînes formatées avec la méthode format
. La méthode format
est plus générale et plus adaptée dans certaines situations. Mais une large partie de sa syntaxe est réutilisable avec des f-chaînes.
La méthode historique de formatage de chaînes en Python, héritée du C et disponible en Java, reste utilisable mais la méthode format
lui est explicitement préférée dans la documentation officielle. En outre, depuis l’apparition des f-chaînes, la méthode classique présente moins d’avantages en concision ou lisibilité.
Les f-chaînes¶
La version 3.6 de Python a introduit une simplification syntaxique très intéressante pour le formatage de chaînes : les chaînes littérales formatées ou encore les f-string (en français, les f-chaînes, terme que j’emploierai). Un simple exemple montre bien la lisibilité de la nouvelle syntaxe :
1 2 3 4 | nom = "Arthur"
age = 30
s = f"Son nom est {nom} et il a {age + 10} ans"
print(s)
|
5 | Son nom est Arthur et il a 40 ans
|
Il s’agit de chaînes littérales habituelles, cf. les quotes ligne 3, mais précédées d’une lettre f
(pour « formaté »). Entre accolades dans la chaîne littérale, on place une expression (et pas seulement une variable) et le remplacement se fait après évaluation de l’expression. Ci-dessus, noter que l’expression age + 10
a été évaluée.
Et c’est tout !
Les chaînes formatées avec la méthode format
¶
Python permet de créer des chaînes formatées basées sur un gabarit.
Considérons le gabarit informel suivant :
Son nom est XXX et il a YYY ans.
Voici le code Python permettant de produire les chaînes formatées basées sur ce gabarit :
1 2 3 4 5 6 7 8 9 | modele = "Son nom est {} et il a {} ans."
a = modele.format("titi", 8)
print(a)
b = modele.format("Arthur", 30)
print(b)
print(modele.format("Alfred", 100))
|
10 11 12 | Son nom est titi et il a 8 ans.
Son nom est Arthur et il a 30 ans.
Son nom est Alfred et il a 100 ans.
|
- Ligne 1 : c’est la traduction Python du gabarit informel
Son nom est XXX et il a YYY ans.
C’est ici une chaîne littérale. Les champs de remplacement sont représentés par des paires d’accolades{}
au lieu de XXX et YYY. - Ligne 3 : construction de la chaîne formatée (qui est affichée ligne 10) à l’aide de la méthode standard
format
. La première paire d’accolades est remplacée dans le gabarit par le premier argument, de même pour la deuxième paire d’accolades. - Lignes 6 et 9 : analogue à la ligne 3.
En résumé, les deux étapes de création d’une chaîne formatée sont
- la construction du gabarit : un gabarit est réalisé en Python avec une chaîne de caractères Python, le plus souvent une chaîne littérale (c’est-à-dire délimitée par des guillemets). Les parties « variables » du gabarit sont placées entre des paires d’accolades. Dans les cas les plus simples, les paires d’accolades ne contiennent rien ;
- la génération de la chaîne formatée : pour « remplir » le gabarit, on appelle la méthode
format
du type chaîne en la préfixant avec la chaîne gabarit. Dans le cas où les accolades sont de la forme{}
ie ne contiennent rien, le remplissage du gabarit est positionnel : le \(k\)-e champ de remplacement du gabarit est remplacé par le \(k\)-e argument de la fonction. La méthodeformat
peut prendre un nombre arbitraire d’arguments. Elle renvoie la chaîne formatée.
Numérotation des arguments¶
On peut placer des numéros au début d’une paire d’accolades présentes dans un gabarit pour se référer aux arguments de la méthode format
suivant leur numéro d’ordre d’apparition en tant qu’arguments :
1 2 | modele = "une {1}, une {2} et une {0}"
print(modele.format("pomme", "poire", "prune"))
|
3 | une poire, une prune et une pomme
|
- Ligne 1 : le gabarit contient trois champs. Par exemple, le champ
{2}
se réfère à l’argument n°2 de la liste des arguments de la méthodeformat
, c’est-à-dire le 3ème argument de la liste.
Le premier argument de la méthode format
est numéroté 0. Chaque paire d’accolades avec son contenu est appelé champ de remplacement. On dit aussi que le gabarit utilise les arguments positionnels de la méthode format
. Chaque nombre placé entre les accolades est appelé nom de remplacement
.
Les arguments de la méthode format
peuvent servir plusieurs fois dans le gabarit ou certains arguments peuvent ne pas servir :
1 2 | modele = "une {1}, une {2} et une {1}"
print(modele.format("pomme", "poire", "prune"))
|
3 | une poire, une prune et une poire
|
- L’argument numéro 0 ne sert pas et l’argument numéro 1 sert deux fois.
Arguments nommés dans un champ de remplacement¶
Pour permettre une plus grande facilité d’utilisation, la méthode format
accepte des arguments dits « nommés » :
1 2 3 | modele = "Son nom est {prenom} et il a {age} ans."
a=modele.format(age=42, prenom= "Arthur")
print(a)
|
4 | Son nom est Arthur et il a 42 ans.
|
- Ligne 1 : la gabarit
modele
possède deux champs de remplacement nommés :{prenom}
et{age}
. - Ligne 2 : la méthode
format
est appelée sur le gabarit. Les arguments de l’appel sont nommés, l’unage
, l’autreprenom
. Les valeurs de ces arguments (respectivement42
et"Arthur"
) vont remplacer les champs du gabarit portant le même nom.
On peut aussi obtenir le même effet si les arguments sont des clés d’un dictionnaire que l’on décompresse dans l’appel :
1 2 3 4 5 | modele = "Son nom est {prenom} et il a {age} ans."
dico={"age":42,"prenom":"Arthur"}
a=modele.format(**dico)
print(a)
|
6 | Son nom est Arthur et il a 42 ans.
|
- Ligne 4 : le dictionnaire est décompressé en des arguments nommés, ce qui donne le même résultat que dans l’exemple précédent.
Dans le même registre, si les arguments sont en fait des variables locales (et donc présentes dans le dictionnaire des variables locales), on peut obtenir le même effet :
1 2 3 4 5 6 | age=42
prenom = "Arthur"
modele = "Son nom est {prenom} et il a {age} ans."
a=modele.format(**locals())
print(a)
|
7 | Son nom est Arthur et il a 42 ans.
|
- Lignes 1-3 : trois variables locales
age
,prenom
,modele
. - Ligne 4 :
locals()
renvoie un dictionnaire dont les clés sont des chaînes de même nom que les variables locales. Comme dans l’exemple précédent, le dictionnaire est décompressé.
Toutefois, la syntaxe des arguments nommés n’est pas aussi souple que celle des f-chaînes puisque le champ de remplacement ne peut contenir qu’une clé de dictionnaire et pas une expression. Ainsi, le code suivant échoue :
1 2 3 | modele = "Son nom est {prenom} et il a {age+10} ans."
a=modele.format(age=42, prenom= "Arthur")
print(a)
|
alors qu’une f-chaîne permettait d’écrire :
1 2 3 4 | age=42
prenom= "Arthur"
a = f"Son nom est {prenom} et il a {age+10} ans."
print(a)
|
5 | Son nom est Arthur et il a 52 ans.
|
Enfin, il est possible d’utiliser à la fois des arguments positionnels numérotés et des arguments nommés mais tous les arguments positionnels doivent être au début de la liste des arguments de la méthode format
:
1 2 3 | modele = "Son nom est {prenom} et, dans {1} mois et {0} jours, il aura {age} ans."
a=modele.format(2, 5, age=42, prenom= "Arthur")
print(a)
|
4 | Son nom est Arthur et, dans 5 mois et 2 jours, il aura 42 ans.
|
Arguments sous forme de liste¶
Examinons l’exemple suivant :
1 2 3 4 | modele = "le {1[2]} est {0}"
objets=["soleil", "stylo", "papier"]
a = modele.format("rouge", objets)
print(a)
|
5 | le papier est rouge
|
- Ligne 1 : dans le champ de remplacement
{1[2]}
, le numéro1
correspond à l’argument numéro 1 de l’appel de la méthodeformat
, ici la liste à la ligne 2. - Lignes 3 et 5 : le champ
{1[2]}
est remplacé parobjets[2]
et le champ{0}
est remplacé par l’argument numéro 0, ie la chaîne"rouge"
.
Un nom de champ de la forme i[j]
signifie que le champ sera remplacé par t[j]
où t
désigne l’objet référencé par l’argument numéro i
de la méthode format
, ce qui suppose que l’objet t
est accessible par l’opération d’indexation (c’est le cas, par exemple, si t
est une liste).
De même, un nom de champ de la forme toto[j]
signifie que le champ sera remplacé par t[j]
où t
désigne l’objet référencé par l’argument nommé toto
de la méthode format
:
1 2 3 4 | modele = "le {objets[2]} est {0}"
t=["soleil", "stylo", "papier"]
a = modele.format("rouge", objets=t)
print(a)
|
5 | le papier est rouge
|
- Ligne 1 : le champ de remplacement
{objets[2]}
, le nomobjets
correspond à un argument nommé de l’appel de la méthodeformat
, ici la listet
à la ligne 2. - Ligne 5 : le champ
{objets[2]}
est remplacé par t[2]².
Échappement des accolades¶
L’accolade est un caractère typographique utilisé dans certains domaines. Par exemple, en mathématiques, le contenu d’un ensemble est représenté en le plaçant entre deux accolades. Ainsi, \(E=\{42,421\}\) est l’ensemble formé de deux éléments 42 et 421.
Ainsi, on peut se demander comment, par exemple, créer un gabarit qui affiche un ensemble mathématique ayant deux éléments ? La réponse est que pour représenter une accolade dans un gabarit, on double l’accolade (ce qu’on appelle un échappement) :
1 | print("E = {{{}, {}}}".format(42,421))
|
2 | E = {42, 421}
|
- Ligne 1 : les deux premières accolades permettent de désigner la première accolade ligne 2 ; la paire d’accolade qui suit est un champ de remplacement qui permet de placer l’élément
42
de la ligne 2.
Une situation typique (mais déjà avancée) où on a besoin d’échapper une accolade apparaît lorsqu’on veut écrire une chaîne formatée qui génère des chaînes formatées.
Spécification de formatage¶
Jusqu’à présent un champ de remplacement (ie une paire d’accolades) dans un gabarit, contient uniquement une référence à un argument de la méthode format
, ce qu’on appelle un nom de champ :
1 2 3 | modele = "une {1}, une {2} et une {1}"
print(modele.format("pomme", "poire", "prune"))
|
- Ligne 1 : chaque champ de remplacement contient juste une référence à un argument de l’appel de
format
.
Mais, cette syntaxe peut être enrichie pour préciser dans chaque champ de remplacement une spécification de formatage, par exemple ici comment on formate une date :
1 2 3 | modele = "{0} {2:02}/{1:02}/{3}"
a=modele.format("dimanche", 9, 1, 2030)
print(a)
|
4 | dimanche 01/09/2030
|
- Ligne 1 : on compte 4 champs de remplacement (les 4 paires d’accolades) ; le 2e et le 3e contiennent une spécification de formatage. Par exemple, dans le champ
{1:02}
, la partie02
à droite du symbole:
est un spécification de formatage.
À droite du nom du champ, il est possible de placer un « spécification de formatage » qui précise comment la valeur à substituer doit être formatée. Pour signifier une spécification de formatage, après le nom du champ, on place le symbole :
(deux-points) ; la spécification de formatage suit le symbole :
jusqu’à l’accolade fermante.
En pratique, une spécification de formatage sert surtout à formater des nombres suivant leurs types (entiers, décimaux, etc) et à gérer l’espacement autour du champ de remplacement.
Reprenons l’exemple précédent :
1 2 3 | modele = "{0} {2:02}/{1:02}/{3}"
a=modele.format("dimanche", 9, 1, 2030)
print(a)
|
4 | dimanche 01/09/2030
|
- Ligne 1 : par exemple, le champ
{1:02}
signifie (ce sera expliqué en détail ultérieurement) que l’argument numéro 1 de la méthodeformat
doit être un nombre et qu’il sera formaté en plaçant un zéro devant le nombre si le nombre n’est constitué que d’un seul chiffre. - Ligne 4 : l’argument numéro 1, qui vaut
9
comme on le voit ligne 2, est donc formaté sous la forme09
.
Spécificateurs de formatage¶
Précisons maintenant ce qu’on entend par « spécificateurs de formatage » :
1 2 3 | modele = "{0} {2:02}/{1:02}/{3}"
a=modele.format("dimanche", 9, 1, 2030)
print(a)
|
4 | dimanche 01/09/2030
|
Ligne 1 :
02
est une spécification de formatage. En réalité, cette spécification se décompose en deux « spécificateurs » :- le spécificateur
0
pour spécifier que le nombre est, si nécessaire, précédé de zéros ; - le spécificateur
2
pour spécifier que deux chiffres seront représentés, cf. le09
ligne 4.
- le spécificateur
Plus généralement, chaque spécification est constituée d’une succession de spécificateurs, jusqu’à 8 spécificateurs. On prendra soin de ne pas placer par mégarde d’espaces dans la spécification. Par ailleurs, l’ordre des spécificateurs n’est pas sans importance. Au lieu de spécificateur, le terme d”option de formatage est aussi employé, et parfois aussi de spécifieur (anglicisme).
Spécification de formatage et f-chaînes¶
Tout ce qui se dira concernant les spécifications de formatage s’applique intégralement aux f-chaînes. Voyons une correspondance sur un exemple :
jsemaine = "dimanche"
jmois = 1
mois = 9
an = 2030
s=f"{jsemaine} {jmois:02}/{mois:02}/{an}"
print(s)
modele="{} {:02}/{:02}/{}"
print(modele.format(jsemaine,jmois,mois,an))
qui affiche
dimanche 01/09/2030
dimanche 01/09/2030
On utilise les spécificateurs de formatage exactement de la même façon dans un cas ou dans l’autre.
Prenons un autre exemple, celui du contrôle du nombre de décimales d’un flottant :
x=22/7
print("{:.2f}".format(x))
print(f"{x:.2f}")
d’où :
1 2 | 3.14
3.14
|
Formatage d’un flottant¶
Le spécificateur de formatage le plus usuel pour les flottants est f
:
1 2 3 | x=22/7
print(x)
print("{:f}".format(x))
|
4 5 | 3.142857142857143
3.142857
|
Le formatage limite la partie décimale a au plus 6 décimales.
La dernière décimale du formatage peut avoir subi un arrondi :
1 2 3 | x=3.141592653589793
print(x)
print("{:f}".format(x))
|
4 5 | 3.141592653589793
3.141593
|
On peut appliquer un formatage flottant à un entier : cela fera apparaître 6 décimales valant toutes 0 :
1 | print("{:f}".format(2020))
|
2 | 2020.000000
|
Contrôle du nombre de chiffres de la partie décimale d’un flottant¶
Le formatage des valeurs des flottants en Python peut faire apparaître jusqu’à 6 décimales. Le programmeur souhaite parfois n’en faire apparaître qu’un nombre plus réduit (ou plus important). Une spécificateur dit de précision permet de contrôler le nombre de chiffres décimaux nécessaires pour représenter un nombre flottant donné :
1 2 3 4 | x=22/7
print(x)
print("{:.2f}".format(x))
print("{:.50f}".format(x))
|
5 6 7 | 3.142857142857143
3.14
3.142857142857142793701541449991054832935333251953125
|
Ligne 3 : la spécification est ici
.2f
et contient deux spécificateurs :- point suivi de la valeur de la précision (un entier positif)
- le type
f
(pour flottant)
Ligne 7 : l’approximation donnée est incorrecte, par défaut, on ne peut pas espérer plus d’une quinzaine de chiffres significatifs correct.
Même comportement si le nombre à formater est de type entier ou s’il possède moins de chiffres significatif que la précision demandée :
1 2 3 4 5 6 | x=42.5
print(x)
print("{:.2f}".format(x))
x=42
print(x)
print("{:.2f}".format(x))
|
7 8 9 10 | 42.5
42.50
42
42.00
|
La représentation obtenue avec le spécificateur de précision est un arrondi et non pas une troncature, autrement dit, c’est la valeur la plus proche du nombre et ayant le nombre de chiffres indiqué qui est retenue :
1 2 3 | x=2/3
print(x)
print("{:.2f}".format(x))
|
4 5 | 0.6666666666666666
0.67
|
- Ligne 5 : si le formatage avait tronqué la valeur, le résultat aurait été
0.66
.
Avec une f-chaîne, le contrôle du nombre de décimales se fait comme suit :
x=22/7
print(f"{x:.2f}")
d’où :
1 | 3.14
|
Les champs¶
La notion de champ¶
On souhaite parfois placer un espacement autour d’un champ de remplacement, souvent pour des raisons d’alignement vertical. Par exemple, pour obtenir les chaînes formatées suivantes
1 2 3 | coloration : blanc → noir
taille : gigantesque → minuscule
vitesse : lent → rapide
|
- Lignes 1-3 : on observe que les mots correspondants sont alignés verticalement ainsi que les séparateurs
:
et→
.
Un gabarit sans spécificateur de formatage permet difficilement d’obtenir le même effet d’alignement vertical :
1 2 3 4 5 | modele = "{2} : {0} → {1}"
print(modele.format("blanc", "noir", "coloration"))
print(modele.format("gigantesque", "minuscule", "taille"))
print(modele.format("lent", "rapide", "vitesse"))
|
6 7 8 | coloration : blanc → noir
taille : gigantesque → minuscule
vitesse : lent → rapide
|
Un champ est une zone de largeur donnée à l’intérieur de laquelle l’élément à remplacer va être placé. Pour placer l’élément numéroté 1 dans un champ de largeur par exemple 12, le champ de remplacement à placer dans le gabarit aura la syntaxe {1:12}
. La largeur est le nombre total de caractères. Par défaut, les caractères autres que les caractères de l’objet de remplacement, c’est-à-dire le bourrage, sont des caractères " "
(espace).
Le formatage ci-dessus a été obtenu avec le code suivant :
1 2 3 4 5 | modele = "{2:10} : {0:12} → {1}"
print(modele.format("blanc", "noir", "coloration"))
print(modele.format("gigantesque", "minuscule", "taille"))
print(modele.format("lent", "rapide", "vitesse"))
|
6 7 8 | coloration : blanc → noir
taille : gigantesque → minuscule
vitesse : lent → rapide
|
Noter que le terme courant de champ est source de confusion. En effet, considérons le gabarit modele = "{2:10} : {0:12} → {1}"
. Alors, on a vu que {2:10}
s’appelle un champ de remplacement et que dans ce champ de remplacement, le nombre 10 représente un champ.
Voyons sur un exemple comment le remplissage se fait en fonction de la dimension de l’objet qui remplace :
1 2 3 4 | modele = "~~~~~{:10}~~~~~"
print(modele.format(''))
print(modele.format("Hello!"))
print(modele.format("Hello World!"))
|
5 6 7 | ~~~~~ ~~~~~
~~~~~Hello! ~~~~~
~~~~~Hello World!~~~~~
|
- Ligne 1 : un champ de 10 caractères sera inséré.
- Ligne 2 : La ligne-étalon pour comparer avec les lignes suivantes.
- Ligne 3 : le champ est rempli des 6 caractères du mot
Hello!
et de 4 espaces (cf. la ligne-étalon) - Ligne 4 et 7 : si le remplacement déborde le champ, le remplacement complet est effectué et aucun blanc n’est placé. Le champ n’a eu aucun effet visible.
Par défaut, chaque élément est placé à gauche du champ. S’il reste de la place dans le champ, le formatage y place des espaces blancs. La largeur du champ est le nombre total de caractères du conteneur. Si l’objet à placer dans le champ est plus large que la largeur du champ, l’objet se place normalement et le champ n’est pas visible.
Alignement à l’intérieur d’un champ, remplissage¶
Par défaut, l’alignement est fait au début du champ, c’est-à-dire à gauche. Il est possible de modifier ce comportement en utilisant un des spécificateurs d’alignement >
ou ^
:
1 2 3 4 5 6 7 8 9 10 11 | modele = "----|{:20}|----"
print(modele.format("bonjour"))
modele = "----|{:>20}|----"
print(modele.format("bonjour"))
modele = "----|{:^20}|----"
print(modele.format("bonjour"))
modele = "----|{:<20}|----"
print(modele.format("bonjour"))
|
12 13 14 15 | ----|bonjour |----
----| bonjour|----
----| bonjour |----
----|bonjour |----
|
- Ligne 1 : rappel du comportement par défaut : alignement à gauche.
- Ligne 4 : spécificateur
>
pour obtenr un alignement à droite du champ. - Ligne 7 : spécificateur
^
pour obtenr un alignement centré dans le champ - Ligne 10 : par homogénéité, le comportement par défaut a aussi son spécificateur :
<
.
Les parties invisibles d’un champ sont, par défaut, remplies de caractères espace.
<
est le spécificateur du comportement par défaut.
Remplissage¶
Il est possible de remplir les parties vides d’un champ avec, par exemple, une suite de caractères comme des points (penser à des points de suspension), des tirets, etc.. Ainsi :
Nom : .......Nobel........
Prénom : .......Alfred.......
Ce caractère est dit caractère de remplissage. Le caractère de remplissage doit immédiatement suivre le séparateur :
et précéder un spécificateur d’alignement.
1 2 3 4 5 6 7 8 | modele = "hier {:_<20} demain"
print(modele.format("maintenant"))
modele = "hier {:_>20} demain"
print(modele.format("maintenant"))
modele = "hier {:_^20} demain"
print(modele.format("maintenant"))
|
9 10 11 | hier maintenant__________ demain
hier __________maintenant demain
hier _____maintenant_____ demain
|
- Lignes 4 : la spécification de formatage est
_>20
donc le remplissage est accompli par le caractère « blanc souligné »_
.
Bien que le comportement par défaut soit l’alignement à gauche, le spécificateur <
d’alignement à gauche doit être mentionné pour remplir par un autre caractère que l’espace :
1 2 | modele = "----|{:_20}|----"
print(modele.format("bonjour"))
|
3 4 5 6 | Traceback (most recent call last):
File "a.py", line 2, in <module>
print(modele.format("bonjour"))
ValueError: Invalid format specifier
|
Pour disposer d’un champ intact, il suffit de remplir avec le caractère vide :
1 2 | modele = "hier {:_<20} demain"
print(modele.format(""))
|
3 | hier ____________________ demain
|
Le caractère accolade fermante ne peut être utilisé pour effectuer un remplissage :
1 2 | modele = "hier {:}<20} demain"
print(modele.format(""))
|
3 4 5 6 | Traceback (most recent call last):
File "a.py", line 2, in <module>
print(modele.format(""))
ValueError: Single '}' encountered in format string
|
Remplissage entre le signe et le premier chiffre¶
C’est une option de remplissage assez rare d’utilisation et permettant de répéter un caractère entre le signe d’un nombre et son premier chiffre :
1 2 | modele="____{:X=9}____{:X=3}____{:X=9}____"
print(modele.format(42, 421, -42))
|
3 | ____XXXXXXX42____421____-XXXXXX42____
|
- Ligne 1 : par exemple, dans le dernier champ de remplacement
{:X=9}
, un champ de largeur 9 est réservé pour placer un nombre ainsi que son signe. L’espace entre le signe éventuel et le premier chiffre du nombre est rempli par des caractèresX
. Ainsi, dans notre exemple, moins de 9 caractères vaudront"X"
, plus précisément, il y en aura 6=9-3 (le nombre-42
utilise 3 caractères).
L’usage le plus courant est de placer des zéros. Cet usage tellement usuel qu’il est aussi rendu possible par le spécificateur de type 0n
où n
est un entier littéral qui indique le nombre total de chiffres à formater :
1 2 3 4 5 | modele="Agent {:0=3}"
print(modele.format(7))
modele="Agent {:03}"
print(modele.format(7))
|
qui affiche
1 2 | Agent 007
Agent 007
|
Les nombres¶
Placer des zéros au début d’un nombre¶
Considérons une date telle que 01/09/2020. On observe que le nombre 1 ou le nombre 9 sont précédés d’un chiffre 0. Il existe une option de formatage qui permet de placer un certain nombre de zéros avant un nombre présent dans une chaîne.
Voici un exemple
1 2 | modele="____{:09}________{:02}_______"
print(modele.format(42, 421))
|
3 | ____000000042________421_______
|
Ligne 1 : la spécificateur
09
indique- qu’un champ de 9 caractères de large et destiné à recevoir un nombre est créé
- que le formatage placera le nombre dans le champ et le fera précéder d’autant de zéros que nécessaire pour remplir le champ.
Ligne 2 : si le nombre déborde du champ, aucun zéro n’est placé.
Si le nombre est négatif, les zéros sont placés, comme on s’y attend, entre le signe moins et le premier chiffre :
1 2 | modele="____{:09}________{:02}_______"
print(modele.format(-42, -421))
|
3 | ____-00000042________-421_______
|
Champs pour des nombres¶
Le principe du champ pour un nombre est le même que pour n’importe quelle chaîne : un nombre (vu comme chaîne de caractères) est placé dans un conteneur ayant une certain largeur, avec la possibilité de choisir entre un placement à gauche, à droite ou au centre du conteneur. Cela permet d’obtenir des effets d’alignement.
1 2 3 4 5 | addition="{:>6}\n+{:>5}\n+{:>5}\n------\n{:>6}"
x=42
y=1999
z=7
print(addition.format(x,y,z, x+y+z))
|
6 7 8 9 10 | 42
+ 1999
+ 7
------
2048
|
- Lignes 1 : tous les nombres sont alignés à droite dans un champ de largeur 6 ou 5 (à cause du signe
+
). - Ligne 6-10 : les nombres sont convenablement alignés.
Il était même plus lisible d’utiliser une chaîne triple :
addition="""\
{:>6}
+{:>5}
+{:>5}
------
{:>6}"""
x=42
y=1999
z=7
print(addition.format(x,y,z, x+y+z))
Format de conversion dans certaines bases¶
Par défaut, les nombres sont formatés en base 10. On souhaite parfois que le nombre soit converti dans l’une des bases suivantes : 2, 8 ou 16. Pour cela il suffit de placer, en dernière position de la spécification de formatage un code de conversion donné dans le tableau suivant :
Base | Spécificateur | Nom usuel |
2 | b |
binaire |
8 | o |
octal |
10 | d |
décimal |
16 | x |
hexadécimal |
16 | X |
hexadécimal |
Voici un exemple d’utilisation de chaque spécificateur :
1 2 3 4 5 6 | print("{:b}".format(42))
print("{:o}".format(42))
print("{:d}".format(42))
print("{:}".format(42))
print("{:x}".format(42))
print("{:X}".format(42))
|
7 8 9 10 11 12 | 101010
52
42
42
2a
2A
|
- Lignes 3 et 4 :le spécificateur
d
correspond au comportement par défaut et peut être omis. - Lignes 5-6 et 11-12 : noter que la seule différence porte sur la casse majuscule/minuscule des chiffres en lettres.
Le spécificateur x
formate les chiffres hexadécimaux sous forme de lettres minuscules (a, b, c, d, e, f) et le spécificateur X
formate les chiffres hexadécimaux sous forme de lettres majuscules (A, B, C, D, E, F).
Un spécificateur de conversion doit toujours être le dernier spécificateur (autrement dit, le plus à droite) de la spécification de formatage.
Conversion dans certaines bases via le spécificateur #
¶
Python (à l’instar de beaucoup de langages) permet de représenter les entiers en base 2, 8 ou 16 sous leur forme littérale. Ainsi, voici les nombres littéraux représentant 42 en Python dans ces bases :
b2 = 0b101010
b8 = 0o52
b16 = 0x2a
B16 = 0X2A
print(b2, b8, b16, B16)
42 42 42 42
Comme on le voit ci-dessus, pour représenter 42 par exemple en base 8, on utilise son écriture en base 8, qui est 52 puisque \(\mathtt{5\times 8^1+2\times 8^0=42}\) et que l’on fait précéder du préfixe 0o.
Ainsi, ces représentations utilisent un préfixe commençant par l’entier 0 et un caractère.
Par ailleurs, le formatage de chaînes permet, à partir d’un entier donné, de produire des chaînes littérales représentant cet entier dans les bases ci-dessus. Par exemple, à partir de l’entier 42, on peut produire par formatage la chaîne 0b101010. Pour cela, on utilise le spécificateur de formatage #
. Par exemple, pour 42 et la base 2 :
fmt = "{0:#b}"
x = 42
s = fmt.format(x)
print(s)
0b101010
Le tableau suivant indique les préfixes pour chaînes littérales et les spécificateurs de formatage utilisés en Python :
Base | Spécificateur | Préfixe |
2 | b |
0b |
8 | o |
0o |
10 | d |
aucun |
16 | x |
0x |
16 | X |
0X |
- Ligne 2 : pour la base 8, noter que le préfixe n’est pas le préfixe classique (qui est
0
et non uno
minuscule).
Ainsi :
1 2 3 4 5 | print("{0:#b}".format(42))
print("{0:#o}".format(42))
print("{0:#d}".format(42))
print("{0:#x}".format(42))
print("{0:#X}".format(42))
|
6 7 8 9 10 | 0b101010
0o52
42
0x2a
0X2A
|
Signalons que lorsque le spécificateur contient le signe #
, on le qualifie de forme alternée, cette terminologie vient du C et de Java.
Notation scientifique des flottants¶
Python permet de représenter des constantes flottantes avec la notation scientifique stricte, à savoir \(a\times 10^n\) où \(a\) est un flottant, appelé parfois mantisse dont la partie entière au signe près est un flottant entre 1 (inclus) et 10 (exclu).
Le spécificateur de formatage en notation scientifique est e
ou E
(cette lettre rappelle la première lettre du mot exposant):
1 2 3 | print("{:f}".format(2020.4248))
print("{:e}".format(2020.4248))
print("{:E}".format(2020.4248))
|
4 5 6 | 2020.424800
2.020425e+03
2.020425E+03
|
La précision de la mantisse est de 6 décimales. L’exposant est placé dans un champ de 2 chiffres. Le spécificateur e
formate la lettre e
en minuscule, et le spécificateur E
formate en majuscule.
Nombre de chiffres en notation scientifique¶
Pour formater un nombre en notation scientifique, en utilisant un spécificateur de précision, on peut contrôler le nombre de chiffres significatifs de la mantisse :
1 | print("{:.2e}".format(2020.4248))
|
2 | 2.02e+03
|
Espace à la place du signe +
¶
On souhaite parfois obtenir un formatage de nombres comme ci-dessous :
a = -42
b = 2024
- Lignes 1-2 : les nombres
42
et2024
sont alignés verticalement du côté gauche.
Plus précisément, pour des raisons d’alignement, on souhaite qu’un nombre négatif soit formaté comme on en a l’habitude (unique signe qui précède le premier chiffre) et qu’un nombre positif soit formaté sans signe mais précédé d’un unique espace.
Le code correspondant à l’exemple ci-dessus est :
1 2 3 4 | modele="{} = {: }"
print(modele.format("a", -42))
print(modele.format("b", 2024))
|
- Ligne 1 : le spécificateur consiste seulement en un espace.
Pour placer un espace à la place du signe +
devant un nombre, on utilise un spécificateur constitué d’un seul caractère espace qui est donc placé matériellement après le séparateur :
et avant l’accolade fermante.
Présence obligatoire du signe plus¶
Parfois, on souhaite que le formatage d’un nombre montre toujours le signe +
ou -
(ce dernier étant obligatoire si le nombre est négatif). Pour cela, il suffit d’utiliser le spécificateur +
1 2 | modele="Rennes : {:+}°C\nMoscou : {:+}°C"
print(modele.format(42, -24))
|
3 4 | Rennes : +42°C
Moscou : -24°C
|
Noter bien qu’un spécificateur de formatage est nécessaire, le code naïf suivant ne fonctionne pas :
1 | print(+42)
|
2 | 42
|
- Ligne 1 : le signe
+
est bien présent - Ligne 2 : le signe
+
est absent : en effet l’expression+42
est évaluée avant d’être affichée.
Séparateur de milliers¶
Pour des raisons de lisibilité, l’usage veut qu’on formate des nombres longs en séparant les chiffres par groupes de trois. Un spécificateur permet cette séparation :
1 2 3 | modele="prix = {:,} euros"
print(modele.format(-6542120359.4521263))
print(modele.format(421))
|
4 5 | prix = -6,542,120,359.4521265 euros
prix = 421 euros
|
Le spécificateur consiste juste en une virgule :
,
Le séparateur est le séparateur utilisé dans le monde anglo-saxon, à savoir une virgule.
La partie décimale n’est pas formatée par groupe de 3 chiffres et il ne semble pas qu’un spécificateur le permette.
Si le nombre a moins de trois chiffres, aucune action de formatage spécifique n’est réalisée.
Séparateur de milliers et respect de la localisation¶
Par défaut, le séparateur des chiffres d’un nombre en bloc de trois chiffres est la virgule. L’usage dans les pays francophones est d’utiliser plutôt un espace. Le spécificateur n
permet de formater en respectant cet usage. Cela suppose que Python soit installé sur une machine où la locale est francophone.
1 2 3 4 5 | import locale
ma_locale = locale.setlocale(locale.LC_ALL, '')
print(ma_locale)
print("{:n}".format(2099.236569633))
|
6 7 | fr_FR.UTF-8
2 099,24
|
- Lignes 2-3 : chargement de la locale pour toutes les catégories et affichage (ligne 6) de la locale utilisable.
- Ligne 5 : le spécificateur consiste juste en
n
- Ligne 7 : on observe la séparation par un espace des deux blocs de chiffres.
La partie décimale a été tronquée à deux chiffres. Même si on tente de garder plus de chiffres significatifs, la partie décimale n’est pas découpée par groupes de 3 chiffres :
1 2 3 4 | import locale
ma_locale = locale.setlocale(locale.LC_ALL, '')
print("{:.10n}".format(2099.236569633))
|
5 | 2 099,23657
|
Pourcentage¶
Le spécificateur %
permet de formater un nombre en pourcentage.
1 | print("{:%}".format(3/4))
|
2 | 75.000000%
|
- On notera que le séparateur décimal est ici un point et non une virgule.
Plus précisément, si \(x\) est le nombre à formater, le formatage consiste :
- à générer en flottant le pourcentage \(100x\)
- à adjoindre le symbole
%
.
L’intérêt semble limité.
Avancé, spécialisé ou secondaire¶
Quelques détails sur les f-chaînes¶
La documentation en ligne sur les f-chaines est dans le document de référence.
Une f-chaîne peut aussi être préfixée par F
majuscule :
1 2 3 4 | nom = "Arthur"
age= 30
s= F"Son nom est {nom} et il a {age+10} ans"
print(s)
|
5 | Son nom est Arthur et il a 40 ans
|
Les f-chaînes et les chaînes brutes sont compatibles :
1 2 3 4 5 6 7 | z="Saut de ligne"
jours = rf"{z} : \n"
print(jours)
jours = fr"{z} : \n"
print(jours)
|
8 9 | Saut de ligne : \n
Saut de ligne : \n
|
Une chaîne triple peut être une f-chaîne :
1 2 3 4 5 6 7 | x=42
s=f"""{x}
{x+1}
{x+2}"""
print(s)
|
8 9 10 | 42
43
44
|
Les accolades entourant l’expression à évaluer ne peuvent pas être vides :
1 2 3 | x=42
s=f"x+1={}"
print(s)
|
4 5 6 7 | File "a.py", line 2
s=f"x+1={}"
^
SyntaxError: f-string: empty expression not allowed
|
L’expression à évaluer ne peut pas contenir un backslash litteral :
1 2 3 | x=10
s=f"y={x+\x05}"
print(s)
|
4 5 6 7 | File "a.py", line 2
s=f"y={x+\x05}"
^
SyntaxError: f-string expression part cannot include a backslash
|
Les expressions lambda peuvent poser un problème syntaxique car elles contiennent un caractère :
qui sert à introduire une spécification de formatage. On peut néanmoins employer des expressions lambda
à condition qu’elles soient entourées de parenthèses :
1 2 3 | L=[42, -81, -12, 31, 82]
s=f"Les négatifs puis les positifs : {sorted(L, key=lambda z:z>=0)}"
print(s)
|
4 | Les négatifs puis les positifs : [-81, -12, 42, 31, 82]
|
Il n’est pas interdit qu’une f-chaîne n’ait aucun champ de remplacement :
1 2 | s=f"Coucou !"
print(s)
|
3 | Coucou !
|
Formatage par défaut d’un objet¶
Jusqu’à présent les remplacements effectués dans un gabarit portent sur des chaînes de caractères ou sur des nombres entiers ou flottants :
1 2 3 | modele = "{} {:02}/{:02}/{}"
a=modele.format("dimanche", 1, 9, 2030)
print(a)
|
4 | dimanche 01/09/2030
|
- Les objets formatés (qui correspondent aux accolades) sont ici soit une chaîne (« dimanche ») soit des entiers comme 2030.
Mais que se passe-t-il si l’argument correspondant à un champ de remplacement est d’un autre type ? Voyons un exemple :
1 2 3 4 5 6 7 8 9 10 11 | class Point:
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name
a = Point(5, 8, "A")
gabarit = "Le point {0.name} ... {0}".format(a)
print(gabarit)
print(str(a))
|
12 13 | Le point A ... <__main__.Point object at 0xb71842ac>
<__main__.Point object at 0xb71842ac>
|
Lorsqu’un argument, disons A, de la méthode format
doit être remplacé dans un gabarit, Python recherche une méthode A.__format__
. Si aucune telle méthode n’a été explicitement définie alors le remplacement est effectué par str(A)
. Par défaut, str(A)
est une chaîne peu lisible pour un utilisateur humain, cf. ligne 12 ou 13.
D’autre part, comme le remplacement est effectué par str(A)
, il est possible de placer dans le champ de remplacement une spécification de formatage valide pour une chaîne. Voici un exemple artificiel :
1 2 3 4 5 6 7 8 9 10 | class Point:
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name
a = Point(5, 8, "A")
gabarit = "Le point {0.name} ... {0:X^60}".format(a)
print(gabarit)
|
11 | Le point A ... XXXXXXXXXXX<__main__.Point object at 0xb715528c>XXXXXXXXXXXX
|
La spécification de formatage X^60
(ligne 9) a entouré de lettres majuscules X
le champ de remplacement dans un champ de largeur 60. Cette utilisation est possible mais ici de peu d’intérêt.
Formatage personnalisé d’un objet¶
Montrons sur un exemple comment on peut définir une méthode spécifique de formatage aux objets d’une classe :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Point:
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name
def __format__(self, format_spec):
if format_spec == "x":
return "x_{0.name} = {0.x}".format(self)
if format_spec == "y":
return "y_{0.name} = {0.y}".format(self)
return "{0.name} = ({0.x}, {0.y})".format(self)
a = Point(9, 8, "A")
print("Le point {0}".format(a))
print("L'abscisse de {0} vaut {1:x}".format(a.name, a))
|
17 18 | Le point A = (9, 8)
L'abscisse de A vaut x_A = 9
|
On définit (lignes 7-12) dans la classe Point
une méthode __format__
. Lorsqu’un champ de remplacement se réfère à un objet de type Point
(lignes 14-16), le remplacement est effectué par ce que renvoie la méthode __format__
. Un champ de remplacement peut même disposer après le symbole :
comme dans le champ {0.x}
d’un spécificateur de formatage sous la forme d’une chaîne format_spec
(ligne 7) et qui est passé en argument (le x
à la ligne 16) à la méthode __format__
.
Génération de gabarits dynamiques¶
Un gabarit Python est une chaîne python qui contient des champs de remplacement matérialisés par des paires d’accolades. Certains programmes peuvent nécessiter des créer des gabarits dynamiques : le gabarit n’est pas créé d’avance dans le code, il est généré pendant l’exécution du programme, le plus souvent en utilisant un gabarit statique et ensuite ce gabarit dynamique est utilisé dans le programme pour générer une chaîne formatée.
Par exemple, on veut générer dynamiquement un gabarit de la forme "{a} et {b}"
où a
et b
sont des entiers positifs ou nuls, comme "{2} et {0}"
qu’on va ensuite utiliser pour générer des chaînes formatées :
1 2 3 4 5 6 7 8 | def creer_gabarit(a,b):
return "{{{0}}} et {{{1}}}".format(a,b)
gabarit=creer_gabarit(2,4)
print(gabarit.format("jaune", "vert", "rose", "noir", "blanc", "rouge"))
gabarit=creer_gabarit(3,0)
print(gabarit.format("jaune", "vert", "rose", "noir", "blanc", "rouge"))
|
9 10 | rose et blanc
noir et jaune
|
Ligne 2 : un gabarit statique
Ligne 4 : un gabarit créé dynamiquement.
Ligne 5 : le dernier gabarit créé sert à créé une chaîne formatée.
Ligne 2 : lorsque capturés par la méthode
format
:- les deux caractères
{{
sont remplacés par une seule accolade{
. - la séquence
{0}
est remplacée par la valeur dea
- les deux caractères
Ligne 4 : gabarit vaut ici
"{2} et {4}"
Lignes 7-9 :
rose et blanc
est ce que renvoie"{2} et {4}".format("jaune", "vert", "rose", "noir", "blanc", "rouge")
Remplacement par un attribut d’argument¶
Un champ de remplacement peut référer un attribut d’un des arguments de la méthode format
. Ainsi, soit le code :
1 2 3 4 5 6 7 8 9 10 11 12 | class Point:
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name
a = Point(5, 8, "A")
b = Point(2, -1, "B")
modele = "x_{0.name} = {0.x}\ny_{1.name} = {1.y}"
s = modele.format(a, b)
print(s)
|
13 14 | x_A = 5
y_B = -1
|
Tout objet de la classe Point
possède trois attributs : x
, y
et name
. Dans un gabarit, tout champ de remplacement (entre accolades) peut référer à l’un de ces attributs. Par exemple, 1.y
réfère à l’attribut y
du 2e argument de format
.
Si l’ordre des champs de remplacement correspond à l’ordre des arguments, la présence des numéros n’est pas indispensable :
1 2 3 4 5 6 7 8 9 10 11 12 | class Point:
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name
a = Point(5, 8, "A")
b = Point(2, -1, "B")
s = "{.name} et {.name}".format(a, b)
print(s)
|
13 | A et B
|
Positions après décompression d’une liste¶
Les arguments de la méthode format
peuvent être récupérés après une décompression de liste avec la syntaxe *L
où L
est une liste, comme pour n’importe quelle fonction admettant une liste à décompresser en argument. Tout numéro d’argument dans un nom de remplacement se réfère aux numéros obtenus après décompression.
1 2 3 4 | modele = "le {0} de couleurs {2}, {1} et {2}"
couleurs=["rouge", "jaune", "vert"]
a = modele.format("drapeau", *couleurs)
print(a)
|
5 | le drapeau de couleurs jaune, rouge et jaune
|
- Ligne 1 : le gabarit se réfère aux arguments numéros 0, 1 et 2.
- Ligne 3 : après décompression de l’argument
couleurs
, c’est comme si la liste des arguments deformat
était :"drapeau", "rouge", "jaune", "vert"
. La substitution se poursuit comme avec des arguments positionnels.
Nombre d’accolades¶
Considérons un gabarit Python de formatage, par exemple
modele = "une {1}, une {2} et une {0}"
Si \(n\) est le plus grand numéro apparaissant entre des accolades (dans l’exemple, c’est 2), il doit y avoir au moins \(n+1\) arguments dans l’appel de la méthode format
, par exemple
modele.format("pomme", "poire", "prune")
Sinon, une exception est levée :
modele = "une {1}, une {2} et une {0}"
ch=modele.format("pomme", "poire")
print(ch)
Traceback (most recent call last):
File "a.py", line 2, in <module>
ch=modele.format("pomme", "poire")
IndexError: tuple index out of range
D’autre part, si une paire d’accolades est vide dans le gabarit, toutes les paires d’accolades suivantes doivent être également vides sinon on reçoit un message d’erreur :
1 | print("{}{1}".format(42,17))
|
2 3 4 5 6 | Traceback (most recent call last):
File "vide_et_non_vide.py", line 1, in <module>
print("{}{1}".format(42,17))
ValueError: cannot switch from automatic field numbering
to manual field specification
|
Enfin, bien que semblant avoir peu d’intérêt, il n’est pas interdit que le gabarit ne reçoive aucune accolade et que pourtant format
reçoive des arguments :
1 2 | s = "19 janvier 2038".format(42)
print(s)
|
3 | 19 janvier 2038
|
Bien sûr, cela n’a aucune action sur le gabarit.
Cela peut être utile lorsque la méthode format
est appliquée à des chaînes générées automatiquement.
Spécification de formatage et champ anonyme¶
Le signe :
entre des accolades marque la présence d’une spécification de formatage. À gauche de :
figure en principe une référence à l’argument de la méthode format
qui va servir de remplacement. La référence à l’argument n’est pas nécessaire si les remplacements s’effectuent suivant l’ordre où les arguments apparaissent écrits dans l’appel :
1 2 3 | modele = "{} {:02}/{:02}/{}"
a=modele.format("dimanche", 1, 9, 2030)
print(a)
|
4 | dimanche 01/09/2030
|
- Ligne 1 : tous les champs sont anonymes puisque rien ne précède le séparateur
:
- ligne 2 : les remplacements sont faits dans l’ordre des arguments de la méthode
format
:"dimanche"
puis1
puis9
et2030
.
Cumuls de spécificateurs¶
Une spécification de formatage peut contenir plusieurs spécificateurs successifs. Voici un exemple commenté :
1 2 3 | modele = "1000 * pi = {:~^ 20,.2f}"
print(modele.format(1000 * 22/7))
print(modele.format(1000 * 333/106))
|
4 5 | 1000 * pi = ~~~~~ 3,142.86~~~~~~
1000 * pi = ~~~~~ 3,141.51~~~~~~
|
Dans l’exemple, il n’y a qu’un champ de remplacement. Sa spécification est :~^ 20,.2f
qui est composée de six spécificateurs :
- un spécificateur de remplissage centré (cf. le spécificateur
^
) par des caractères~
- l’espace (résultat : place un unique espace avant un nombre positif)
- un champ de largeur 20
- le séparateur de milliers (spécificateur virgule)
- précision (2 décimales)
- type flottant
L’ordre des spécificateurs n’est pas arbitraire ; de gauche à droite, on trouve des spécificateurs
- d’alignement (
<
,^
,>
et plus rarement=
), précédé d’un unique caractère de remplissage - de signe (
+
ou espace) - de préfixe de conversion dans certaines bases (
#
) - de remplissage par des zéros (
0
) - de largeur de champ (un entier)
- de groupement par blocs de 3 chiffres de la partie entière (une virgule)
- de précision (de la forme
.n
oùn
est un entier) - de type (par exemple
f
,x
,n
ou%
)
Les spécificateurs sont optionnels, certains peuvent être omis (comme le type). Certains spécificateurs ne s’appliquent qu’à des nombres, voire des nombres d’un certain type. La plupart du temps, le bon sens permet de trancher.
Champs de remplacement emboîtés¶
Considérons le gabarit suivant :
gabarit= "__{0:.>20}____{1:-^10}__"
Voici un exemple d’utilisation de ce gabarit :
gabarit= "__{0:.>20}____{1:-^10}__"
print(gabarit.format(2020, 2038))
3 | __................2020____---2038---__
|
Décrivons ce gabarit. Il contient deux champs de remplacement, à savoir {0:.>20}__
et {1:-^10}
; ces champs sont séparés. Chacun de ces champs est composé
- de la référence à l’argument de la méthode
format
(0 puis 1) - du séparateur
:
- d’une spécification de formatage (
.>20
et-^10
).
A priori, des champs de remplacement sont toujours séparés. Cependant, il est possible, sous certaines conditions, de placer des champs de remplacement dans une spécification de format :
1 2 | gabarit0= "__{0:{2}}____{1:{3}}__"
print(gabarit0.format(2020, 2038, ".>20", "-^10"))
|
- Ligne 1 : le premier champ de remplacement
{0:{2}}
a une spécification{2}
qui contient un autre champ de remplacement (les accolades). On parle alors de champs emboîtés.
Action d’un champ emboîté¶
Exécutons l’exemple précédent :
1 2 | gabarit0= "__{0:{2}}____{1:{3}}__"
print(gabarit0.format(2020, 2038, ".>20", "-^10"))
|
3 | __................2020____---2038---__
|
On observe que le résultat est le même que la première utilisation de gabarit
ci-dessus. Le processus d’exécution est le suivant :
- tout champ, emboîté ou non, a un nom qui réfère à l’argument de la méthode
format
. Par exemple, le champ{2}
ci-dessus réfère à l’argument".>20"
, l’argument n°2 deformat
. - les champs de remplacement emboîtés sont d’abord remplacés par les arguments correspondants ce qui produit un gabarit implicite, ici
"__{0:.>20}____{1:-^10}__"
- suivant le processus habituel, les champs non emboîtés sont remplacés par les arguments correspondants.
Codes équivalents¶
Le code ci-dessus est équivalent à :
1 2 3 4 | gabarit0="__{0:{2}}____{1:{3}}__"
gabarit= gabarit0.replace("{2}",".>20").replace("{3}","-^10")
print(gabarit)
print(gabarit.format(2020, 2038))
|
5 6 | __{0:.>20}____{1:-^10}__
__................2020____---2038---__
|
Intérêt¶
L’intérêt des champs emboîtés est que cela permet de générer des spécificateurs de format dynamiques, par exemple avec des champs de largeur paramétrable.
Un exemple typique, lu dans ce message de forum, permet de paramétrer le nombre de décimales d’un flottant :
1 2 3 4 | modele="{:.{}f}"
for prec in [0, 3, 6, 9]:
print(modele.format(22/7,prec))
|
5 6 7 8 | 3
3.143
3.142857
3.142857143
|
- Ligne 1 : un même gabarit va permettre de générer les chaînes de largeur variable (lignes 5-8)
- Ligne 4 : on formate successivement
"{22/7:.{0}f}"
, puis"{22/7:.{3}f}"
, etc.
On peut faire des formatages plus complexes :
1 2 3 4 | modele = "__{0:{1}{2}{3}{4}{5}}__"
x=2038
print(modele.format(x, '?', '>', ' ', 42,','))
print(modele.format(x, '~', '^', '+', 20,'e'))
|
5 6 | __???????????????????????????????????? 2,038__
__~~~+2.038000e+03~~~~__
|
Drapeau de conversion¶
Soit le code :
1 | print("{0}{1:.^20}".format("Hello", "Rose"))
|
2 | Hello........Rose........
|
La chaîne « {0}{1:.^20} » comporte 2 champs de remplacement. Examinons par exemple le 2e champ : {1:.^20}
. Il comporte deux composantes :
- un numéro d’argument : 1
- un spécificateur de formatage :
.^20
Cette syntaxe peut-être enrichie. Pour que mes explications ne soient pas trop abstraites, voici la nouveauté sur un exemple :
print("{0}{1!a:.^20}".format("Hello", "Rose"))
qui affiche
Hello.......'Rose'.......
Un champ de remplacement peut disposer d’une troisième composante : un drapeau de conversion. Ce drapeau est placé en 2ème position, entre le représentant de l’argument et avant les deux-points qui précèdent le spécificateur de formatage, cf. ci-dessus, les deux caractères !a
.
Un drapeau peut prendre une des trois formes suivantes :
!s
ou !r
ou !a
Voici un exemple :
1 2 3 | print("{0}{1!s:.^20}".format("Hello", "Rose"))
print("{0}{1!r:.^20}".format("Hello", "Rose"))
print("{0}{1!a:.^20}".format("Hello", "Rosé"))
|
4 5 6 | Hello........Rose........
Hello.......'Rose'.......
Hello.....'Ros\xe9'......
|
Lorqu’un objet X à remplacer est associé à un drapeau de conversion, l’objet X est converti en une chaîne S
et c’est la chaîne qui fait l’objet du remplacement. La chaîne S
dépend du drapeau :
- si le drapeau est
!s
alors S eststr(X)
- si le drapeau est
!r
alors S estrepr(X)
- si le drapeau est
!a
alors S estascii(X)
oùascii
est une fonction de la bibliothèque standard (par exemple, en français, ça réécrit s’il y a un accent).
Dans les cas usuels, le drapeau !s
n’a pas d’action :
1 2 | print("{0}{1!s:.^20}".format("Hello", "Rose"))
print("{0}{1:.^20}".format("Hello", "Rose"))
|
3 4 | Hello........Rose........
Hello........Rose........
|
Il se peut, bien sûr, que le drapeau soit seul dans le champ de remplacement :
1 | print("C'est {!a}".format("codé"))
|
2 | C'est 'cod\xe9'
|
Intérêt du drapeau de conversion¶
Soit un appel de la méthode format
de la forme gabarit.format
où gabarit est une chaîne. Si gabarit admet un champ de remplacement par un objet X
alors le champ est remplacé par le retour d’un appel de X.__format__
. Parfois ce comportement par défaut n’est pas souhaité et pour l’éviter, on peut placer un drapeau dans le champ de remplacement.
Par exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Point:
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name
def __format__(self, format_spec):
if format_spec == "x":
return "x_{0.name} = {0.x}".format(self)
if format_spec == "y":
return "y_{0.name} = {0.y}".format(self)
return "{0.name} = ({0.x}, {0.y})".format(self)
a = Point(9, 8, "A")
print("Le point {0}".format(a))
print(str(a))
print("Le point {0!s}".format(a))
|
18 19 20 | Le point A = (9, 8)
<__main__.Point object at 0xb713d28c>
Le point <__main__.Point object at 0xb713d28c>
|
- Pas de drapeau : la méthode
__format__
de Point est appelée - Le drapeau
!s
empêche l’appel de la méthode__format__
et le champ est remplacé parstr(a)
Troncature de chaîne¶
On peut vouloir qu’un champ de remplacement reçoive une chaîne dont on ne garde qu’un certain nombre des premiers caractères :
1 2 3 | semaine=['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
for i in range(7):
print("{0:.3} {1}/10/2020".format(semaine[i], i+1))
|
4 5 6 7 8 9 10 | lun 1/10/2020
mar 2/10/2020
mer 3/10/2020
jeu 4/10/2020
ven 5/10/2020
sam 6/10/2020
dim 7/10/2020
|
- Lignes 1-2 : seuls les 3 premiers caractères du jour de la semaine seront utilisés pour remplacer le champ.
Cette utilisation de troncature semble être assez rare.
La même syntaxe sert à contrôler le nombre de décimales d’un flottant.
Formatage et concaténation¶
Le formatage de chaînes permet accessoirement de concaténer ou de joindre des chaînes
1 2 3 4 5 | modele = "{}{}{}"
print(modele.format("Uni","Ver","Sel"))
modele = "{}-{}-{}"
print(modele.format("Uni","Ver","Sel"))
|
6 7 | UniVerSel
Uni-Ver-Sel
|
Considérations sur les chaînes formatées¶
Deux systèmes de formatage concurrents¶
Deux systèmes de formatage de chaînes coexistent en Python 3. Le système occasionnellement qualifié d”ancien dans la documentation officielle est basé sur le style des chaînes formatées du langage C ou de Java utilisant l’opérateur %
de substitution.
Selon la Documentation officielle :
- le système classique comprendrait des « bizarreries » qui conduiraient à un certain nombre de confusions
- la nouvelle syntaxe permet d’éviter ces erreurs tout en se montrant plus puissante et extensible.
L’ancien système reste cependant très courant d’utilisation, voire préféré, et on ne peut pas considérer qu’il soit obsolescent, voir par exemple Pythons many ways of string formatting — are the older ones (going to be) deprecated? ainsi que le commentaire de Ned Batchelder au message Nicest way to pad zeroes to string.
D’ailleurs, la documentation des versions 3.1 et 3.2 de Python affirmait :
The formatting operations described here are obsolete and may go away in future versions of Python. Use the new String Formatting in new code.
Toutefois cette mention a disparu des versions suivantes. A propos de cette disparition, on pourra lire la discussion Why was the warning about % formatting toned down in newer Python docs?.
Selon l’article Be Careful with Python’s New-Style String Format, la nouvelle syntaxe des chaînes formatées vient du langage .NET et a été adoptée par le langage Rust.
Selon Sam et Max, il faudrait utiliser, selon les situations, la syntaxe avec la méthode format
ou bien la syntaxe classique.
Seule, la syntaxe classique est utilisée pour le formatage des objets de type byte et bytearray, cf. printf-style Bytes Formatting.
Documentation sur le formatage de chaîne¶
La documentation officielle sur le formatage de chaînes est dispersée en trois emplacements, dans deux documents différents et non accessible par la table des matières. Voici les liens
- les f-chaînes
- le formatage avec la méthode format
- les chaînes formatées classiques
La documentation de la bibliothèque standard contient une section spécifique à la traduction de chaînes formatées de style classique en des chaînes formatées avec la méthode format
.
Contexte d’utilisation de chaînes formatées¶
Une chaîne formatée est une chaîne comme les autres. Elle n’est pas exclusivement destinée à être affichée, bien que ce sera assez fréquent en pratique. Une chaîne formatée possède toutes les propriétés d’une chaîne, par exemple une longueur :
1 2 3 4 5 6 7 8 | modele = "Son nom est {} et il a {} ans."
naissance= 1990
aujourdhui= 2020
a = modele.format("Arthur", aujourdhui - naissance)
print(len(a))
print(a)
|
9 10 | 34
Son nom est Arthur et il a 30 ans.
|
Exemple typique¶
Les chaînes formatées servent souvent à remplacer des valeurs dans une chaîne-type puis à effectuer l’affichage (en général avec print). Par exemple :
1 2 3 4 5 6 | modele = "{0} = {2}, {1} = {3} => {0} + {1} = {4}"
x=42
y=18
print(modele.format("x", "y", x, y, x+y))
print(modele.format("too", "titi", x, y, x+y))
|
7 8 | x = 42, y = 18 => x + y = 60
too = 42, titi = 18 => too + titi = 60
|
Un affichage pourrait être produit sans utilisation de chaînes formatées mais le code serait moins lisible :
1 2 3 4 5 | modele = "{0} = {2}, {1} = {3} => {0} + {1} = {4}"
x=42
y=18
print("x =", x, ", y =", y, "=> x + y =", x+y)
|
6 | x = 42 , y = 18 => x + y = 60
|
En outre, la virgule juste après la valeur de x, n’est pas placée comme attendue (il y a un blanc de trop).
Exemple plus complexes¶
Les chaînes formatées servent aussi à construire des chaînes complexes, représentant par exemple des alignements particuliers, pas forcément dans un but immédiat d’affichage. Ainsi, pour réaliser une table de multiplication comme ceci,
---------------------------------------------------------------------
| 1 x 1 = 1| 2 x 1 = 2| 3 x 1 = 3| 4 x 1 = 4| 5 x 1 = 5|
| 1 x 2 = 2| 2 x 2 = 4| 3 x 2 = 6| 4 x 2 = 8| 5 x 2 = 10|
| 1 x 3 = 3| 2 x 3 = 6| 3 x 3 = 9| 4 x 3 = 12| 5 x 3 = 15|
| 1 x 4 = 4| 2 x 4 = 8| 3 x 4 = 12| 4 x 4 = 16| 5 x 4 = 20|
| 1 x 5 = 5| 2 x 5 = 10| 3 x 5 = 15| 4 x 5 = 20| 5 x 5 = 25|
| 1 x 6 = 6| 2 x 6 = 12| 3 x 6 = 18| 4 x 6 = 24| 5 x 6 = 30|
| 1 x 7 = 7| 2 x 7 = 14| 3 x 7 = 21| 4 x 7 = 28| 5 x 7 = 35|
| 1 x 8 = 8| 2 x 8 = 16| 3 x 8 = 24| 4 x 8 = 32| 5 x 8 = 40|
| 1 x 9 = 9| 2 x 9 = 18| 3 x 9 = 27| 4 x 9 = 36| 5 x 9 = 45|
| 1 x 10 = 10| 2 x 10 = 20| 3 x 10 = 30| 4 x 10 = 40| 5 x 10 = 50|
---------------------------------------------------------------------
| 6 x 1 = 6| 7 x 1 = 7| 8 x 1 = 8| 9 x 1 = 9|10 x 1 = 10|
| 6 x 2 = 12| 7 x 2 = 14| 8 x 2 = 16| 9 x 2 = 18|10 x 2 = 20|
| 6 x 3 = 18| 7 x 3 = 21| 8 x 3 = 24| 9 x 3 = 27|10 x 3 = 30|
| 6 x 4 = 24| 7 x 4 = 28| 8 x 4 = 32| 9 x 4 = 36|10 x 4 = 40|
| 6 x 5 = 30| 7 x 5 = 35| 8 x 5 = 40| 9 x 5 = 45|10 x 5 = 50|
| 6 x 6 = 36| 7 x 6 = 42| 8 x 6 = 48| 9 x 6 = 54|10 x 6 = 60|
| 6 x 7 = 42| 7 x 7 = 49| 8 x 7 = 56| 9 x 7 = 63|10 x 7 = 70|
| 6 x 8 = 48| 7 x 8 = 56| 8 x 8 = 64| 9 x 8 = 72|10 x 8 = 80|
| 6 x 9 = 54| 7 x 9 = 63| 8 x 9 = 72| 9 x 9 = 81|10 x 9 = 90|
| 6 x 10 = 60| 7 x 10 = 70| 8 x 10 = 80| 9 x 10 = 90|10 x 10 = 100|
---------------------------------------------------------------------
Typiquement, le code va utiliser un gabarit qui représente l’élément formaté élémentaire qui sera répété pour constituer la totalité de la table, ici tel un élément serait représenté par exemple par | 6 x 1 = 6
qui correspond au gabarit Python "{0}|{1}{2:2} x {3:2} = {4:3}{5}"
.
La table de multiplication est placée dans une grande chaîne au fur et à mesure. Ensuite, cette chaîne est affichée en une seule instruction.
Exemples d’utilisation de chaînes formatées¶
Voici une liste de situations typiques d’utilisation de chaînes formatées :
date, par exemple mardi 19/01/2038
heure, par exemple
03:14:42
numéro de téléphone :
08-81-42-99-00
grilles en tous genres :
- sudoku,
- tables de multiplication
- conjugaison de verbes
- calendrier
- tabulation des valeurs d’une fonction
Opération comme :
45
+ 253
+ 940
-----
1238