Objectifs du chapitre
À la fin de ce chapitre, vous serez capable de:
- écrire une chaîne de caractères avec des guillemets simples, doubles ou triples, y placer un retour à la ligne, une tabulation ou une barre oblique inverse, et reconnaître une chaîne brute;
- atteindre un caractère par son indice, positif ou négatif, mesurer une chaîne avec
lenet lire uneIndexErrorsans paniquer; - extraire une tranche avec ses trois bornes, pas négatif compris, et prévoir exactement les caractères obtenus;
- expliquer pourquoi une chaîne est immuable, et construire une nouvelle chaîne au lieu de vouloir modifier l'ancienne;
- utiliser les méthodes usuelles (
upper,strip,replace,find,count,split,join) et composer un affichage propre avec une f-string, y compris un nombre arrondi à deux décimales; - parcourir une chaîne caractère par caractère ou par indices, comparer deux chaînes, et dire ce que
lencompte pour un mot accentué.
Une chaîne est une suite de caractères
Vous avez déjà écrit du texte dans vos programmes: print("Bonjour"), un message d'invite pour input, un commentaire affiché à l'écran. Jusqu'ici, ce texte était un bloc opaque, quelque chose qu'on affiche tel quel. Ce chapitre l'ouvre.
Prenez un problème que vous saurez résoudre à la fin du chapitre et pas au début. Un formulaire d'inscription vous transmet une adresse électronique, "alice.dupont@unil.ch", et vous devez en tirer l'identifiant (alice.dupont), le domaine (unil.ch), le prénom et le nom de famille, puis afficher une ligne propre: DUPONT Alice. Rien de tout cela n'est faisable avec print et input seuls. Il faut pouvoir regarder à l'intérieur du texte: savoir combien il a de caractères, atteindre le dix-septième, prendre tout ce qui précède l'arobase, mettre une portion en majuscules.
Le mot caractère mérite d'être pris au sérieux. Un caractère, ce n'est pas seulement une lettre: l'espace en est un, le point d'interrogation aussi, le chiffre 7 aussi (et il ne vaut pas le nombre 7, nous y reviendrons), et la lettre é est un caractère à elle toute seule. Une chaîne est une suite de ces objets-là, rien de plus.
Écrire une chaîne: guillemets simples, doubles, triples
Python accepte trois façons d'écrire une chaîne dans un programme. Les deux premières sont strictement équivalentes: 'Lausanne' et "Lausanne" produisent exactement le même objet. Disposer des deux est commode lorsque le texte contient lui-même un guillemet: "l'aube" s'écrit sans effort avec des guillemets doubles, et 'il a dit "oui"' avec des guillemets simples.
La troisième forme utilise trois guillemets, """…""" ou '''…''', et a une propriété que les autres n'ont pas: elle peut s'étendre sur plusieurs lignes, retours à la ligne compris.
prenom = "Alice"
ville = 'Lausanne'
message = """Bonjour Alice,
votre note est 4.5.
À bientôt."""
print(prenom)
print(ville)
print(message)
Dans tout ce chapitre, la sortie du programme est donnée dans un bloc séparé, juste après le code. Voici ce que Python affiche:
Alice
Lausanne
Bonjour Alice,
votre note est 4.5.
À bientôt.
Remarquez la dernière ligne du programme: un seul print a produit trois lignes à l'écran, parce que la chaîne message contient elle-même deux retours à la ligne. Une chaîne peut contenir des retours à la ligne; c'est une suite de caractères, et le retour à la ligne en est un.
Les séquences d'échappement
Comment mettre un retour à la ligne dans une chaîne écrite sur une seule ligne? Et comment mettre un guillemet double dans une chaîne délimitée par des guillemets doubles? Python prévoit pour cela des séquences d'échappement: une barre oblique inverse (backslash, \) suivie d'une lettre, que Python remplace par un caractère unique au moment où il lit le programme.
| Séquence | Caractère obtenu |
|---|---|
\n | retour à la ligne (newline) |
\t | tabulation (tab) |
\\ | une barre oblique inverse |
\" | un guillemet double |
\' | un guillemet simple |
# \n passe a la ligne, \t tabule, \\ donne une barre, \" donne un guillemet droit
print("Nom\tNote")
print("Alice\t4.5")
print("Elle a dit \"d'accord\" puis elle est partie.")
print("Le fichier est C:\\notes\\table.txt")
Nom Note
Alice 4.5
Elle a dit "d'accord" puis elle est partie.
Le fichier est C:\notes\table.txt
Deux choses méritent d'être notées. D'abord, \t aligne la deuxième colonne: c'est le moyen le plus rudimentaire de fabriquer un tableau, et nous verrons plus loin un moyen bien meilleur. Ensuite, "\\" s'écrit avec deux caractères dans le programme mais n'en contient qu'un: la barre oblique inverse doit être elle-même échappée, puisque seule, elle annonce une séquence.
Les chaînes brutes
Ce doublement devient pénible dès qu'un texte contient beaucoup de barres obliques inverses — un chemin de fichier Windows, typiquement. Pire, il est dangereux: si vous oubliez de doubler, Python ne se plaint pas toujours, il interprète.
chemin_normal = "C:\notes\table.txt"
chemin_brut = r"C:\notes\table.txt"
print(chemin_normal)
print(chemin_brut)
print(len(chemin_normal), len(chemin_brut))
C:
otes able.txt
C:\notes\table.txt
16 18
La première ligne est un désastre silencieux: \n est devenu un retour à la ligne et \t une tabulation, si bien que le «chemin» affiché n'a plus rien à voir avec le fichier voulu, et qu'il compte 16 caractères au lieu de 18. Le préfixe r (pour raw, brut) devant les guillemets dit à Python de prendre les barres obliques inverses au pied de la lettre. Utilisez-le pour tout chemin de fichier et, plus tard, pour les expressions régulières.
Atteindre un caractère: l'indexation
Une chaîne étant une suite ordonnée, chaque caractère a une position, que l'on appelle son indice (index). L'opération fondamentale du chapitre s'écrit avec des crochets: chaine[i] est le caractère d'indice i.
Numéroter à partir de zéro surprend tout le monde au début, et c'est la source d'erreur numéro un de ce chapitre. La contrepartie de cette convention est que l'indice d'un caractère est le nombre de caractères qui le précèdent: le premier n'en a aucun devant lui, d'où l'indice 0.
Python offre en plus une seconde numérotation, à partir de la fin: chaine[-1] est le dernier caractère, chaine[-2] l'avant-dernier. Elle est extrêmement commode, parce que «le dernier caractère» se dit alors sans connaître la longueur.
mot = "PROGRAMME"
print(len(mot))
print(mot[0])
print(mot[3])
print(mot[8])
print(mot[-1])
print(mot[-9])
9
P
G
E
E
P
Deux lectures confirment la figure: mot[8] et mot[-1] donnent le même caractère E, et mot[0] et mot[-9] le même caractère P. La règle de conversion est simple: pour une chaîne de longueur , l'indice négatif désigne la même case que l'indice positif . Ici correspond à , et à .
Quand l'indice sort de la chaîne
Que se passe-t-il si l'on demande mot[9]? Il n'y a pas de dixième case. Python ne renvoie pas une valeur par défaut, ne renvoie pas une chaîne vide: il arrête le programme avec une erreur.
mot = "PROGRAMME"
print(len(mot))
print(mot[9])
9
Traceback (most recent call last):
File "indices.py", line 3, in <module>
print(mot[9])
~~~^^^
IndexError: string index out of range
Lisez cette erreur ligne par ligne, comme au chapitre 1. Traceback (most recent call last) annonce la pile des appels. La ligne File "indices.py", line 3 donne le fichier et la ligne fautive — chez vous, le chemin complet de votre fichier apparaît à la place de indices.py. La ligne suivante reproduit le code, et les caractères ~~~^^^ soulignent précisément l'opération qui a échoué: le ^^^ pointe sur [9], pas sur print. Enfin vient l'information utile: le type de l'erreur, IndexError, et son message, string index out of range, «indice de chaîne hors limites».
Notez aussi que le 9 de la ligne 2 a bien été affiché: Python exécute les instructions dans l'ordre et s'arrête au moment où il bute. Ce qui précède l'erreur a eu lieu; ce qui la suit n'aura pas lieu.
La variable mot contient la chaîne PROGRAMME, de neuf caractères. Que vaut ?
Les tranches
Extraire un seul caractère est rarement suffisant. Pour l'adresse électronique de l'introduction, ce qu'il faut, c'est «tout ce qui est avant l'arobase». Python fournit pour cela les tranches (slices), qui utilisent les mêmes crochets avec deux ou trois bornes séparées par des deux-points.
La borne de fin exclue déroute au début, mais elle a trois avantages qui expliquent qu'on la retrouve partout en informatique. Premièrement, le nombre de caractères obtenus est simplement fin - debut: mot[3:7] en donne quatre, sans calcul. Deuxièmement, deux tranches consécutives se recollent sans trou ni doublon: mot[0:3] suivi de mot[3:7] couvre exactement les indices 0 à 6, et le 3 n'apparaît qu'une fois. Troisièmement, chaine[0:len(chaine)] est la chaîne entière, sans le - 1 ou le + 1 qu'il faudrait sinon écrire. C'est la même convention que range(debut, fin), vue au chapitre 3.
La figure 5.2 suggère une image qui vaut la peine d'être retenue: les bornes d'une tranche ne désignent pas des cases mais des barrières entre les cases. Il y a dix barrières pour neuf cases, numérotées de 0 à 9; la barrière 0 est avant le premier caractère, la barrière 9 après le dernier. mot[3:7] prend tout ce qui se trouve entre la barrière 3 et la barrière 7, soit quatre cases. Avec cette image, la borne de fin n'est plus «exclue», elle est simplement située ailleurs que les cases.
mot = "PROGRAMME"
print(mot[0:3])
print(mot[3:7])
print(mot[7:9])
print(mot[:4])
print(mot[4:])
print(mot[:])
PRO
GRAM
ME
PROG
RAMME
PROGRAMME
Les trois premières lignes découpent le mot en trois morceaux qui se recollent exactement: PRO, GRAM, ME. Les trois suivantes montrent qu'une borne peut être omise: quand le début manque, Python prend le début de la chaîne; quand la fin manque, il va jusqu'au bout; quand les deux manquent, on obtient la chaîne entière. mot[:4] se lit «les quatre premiers», mot[4:] «tout à partir du cinquième».
Le pas, et le pas négatif
La troisième borne, le pas, fonctionne comme le troisième argument de range: mot[debut:fin:pas] avance de pas en pas au lieu d'avancer de un en un. Un pas négatif parcourt la chaîne à l'envers, et c'est là que se trouve l'idiome le plus célèbre de Python.
mot = "PROGRAMME"
print(mot[0:9:2])
print(mot[1:8:3])
print(mot[-3:])
print(mot[:-3])
print(mot[::-1])
print(mot[7:2:-1])
print(mot[
PORME
RRM
MME
PROGRA
EMMARGORP
MMARG
Vérifions caractère par caractère, c'est le seul moyen de vraiment comprendre.
mot[0:9:2]visite les indices 0, 2, 4, 6, 8, c'est-à-direP,O,R,M,E: la chaînePORME.mot[1:8:3]part de 1 et avance de trois: 1, 4, 7, soitR,R,M. L'indice suivant serait 10, au-delà de la borne 8: on s'arrête.mot[-3:]part de la troisième case en partant de la fin et va jusqu'au bout: indices 6, 7, 8, soitMME. C'est la façon idiomatique de dire «les trois derniers».mot[:-3]s'arrête avant cette même case: indices 0 à 5, soitPROGRA. Les deux tranchesmot[:-3]etmot[-3:]se recollent en la chaîne entière, exactement commemot[:4]etmot[4:].mot[::-1]est l'idiome du retournement: sans bornes et avec un pas de −1, Python part de la fin et remonte jusqu'au début, d'oùEMMARGORP. Retenez-le, il sert à chaque fois qu'on teste un palindrome.mot[7:2:-1]remonte de l'indice 7 vers l'indice 2 exclu: 7, 6, 5, 4, 3, soitMMARG. Avec un pas négatif, c'est la borne de fin qui reste exclue, donc l'indice 2 n'y est pas.mot[5:2]demande d'aller de 5 à 2 avec le pas positif par défaut: il n'y a rien entre les deux dans ce sens. Python ne proteste pas, il renvoie la chaîne vide"", etprintaffiche une ligne blanche — c'est la dernière ligne, vide, de la sortie ci-dessus.
Déplacez les bornes et le pas sur la chaîne «PROGRAMME». Les caractères retenus sont surlignés dans l'ordre où Python les parcourt. Le pas nul, la tranche vide et les bornes hors limites sont traités explicitement.
Prenez cinq minutes avec cet explorateur avant de continuer, en essayant de prévoir le résultat avant de bouger le curseur. Quatre situations valent le détour: une borne de fin plus petite que la borne de début avec un pas positif (résultat vide); la même avec un pas négatif (résultat non vide, à l'envers); une borne bien au-delà de 9 (ramenée au bord, aucune erreur); et le pas nul, seul cas où Python refuse et lève une ValueError.
La variable mot contient PROGRAMME. Combien de caractères la tranche contient-elle?
Une chaîne ne se modifie pas
Vous savez lire un caractère. Écrivons maintenant: mettons une majuscule au premier caractère de "programme". La tentation est irrésistible, et elle échoue.
mot = "programme"
mot[0] = "P"
print(mot)
Traceback (most recent call last):
File "immuable.py", line 2, in <module>
mot[0] = "P"
~~~^^^
TypeError: 'str' object does not support item assignment
Le message est parfaitement clair une fois traduit: «un objet de type str ne permet pas l'affectation d'un élément». Ce n'est pas un IndexError — l'indice 0 est parfaitement valide — c'est un TypeError: l'opération demandée n'existe pas pour ce type. La lecture mot[0] est autorisée, l'écriture mot[0] = … ne l'est pas.
La solution n'est pas un contournement: c'est la seule façon de faire, et elle est parfaitement naturelle. On fabrique la chaîne voulue à partir de morceaux de l'ancienne, et on donne le résultat au même nom.
mot = "programme"
mot = "P" + mot[1:] # on construit une nouvelle chaine
print(mot)
Programme
La ligne 2 se lit de droite à gauche: Python évalue d'abord mot[1:], ce qui crée la chaîne "rogramme"; il concatène "P" devant, ce qui crée encore une nouvelle chaîne "Programme"; et seulement alors il fait pointer le nom mot vers ce dernier objet. L'ancienne chaîne "programme" n'a pas été touchée; simplement, plus personne ne la désigne, et Python finira par récupérer la mémoire qu'elle occupe.
La variable mot vaut «programme». Remettez dans l'ordre les étapes que Python exécute pour la ligne .
Glissez les éléments pour les mettre dans le bon ordre
- L'ancienne chaîne «programme», que plus aucun nom ne désigne, est abandonnée telle quelle
- Il concatène «P» et cette chaîne, ce qui crée encore une nouvelle chaîne «Programme»
- Il fait pointer le nom mot vers ce dernier objet
- Python évalue la tranche mot[1:], ce qui crée une nouvelle chaîne «rogramme»
Les méthodes usuelles
Une méthode est une fonction attachée à un objet, que l'on appelle en écrivant l'objet, un point, le nom de la méthode et des parenthèses: nom.upper(). Vous en avez le mode d'emploi complet dans la documentation officielle, section «Text Sequence Type — str»; voici les neuf dont vous aurez besoin tout de suite. Toutes respectent la règle du paragraphe précédent: elles renvoient une valeur, elles ne modifient pas la chaîne.
Changer la casse: upper, lower, capitalize
upper() renvoie la chaîne tout en majuscules, lower() tout en minuscules, capitalize() avec une majuscule initiale et le reste en minuscules. Elles traitent correctement les lettres accentuées: "é".upper() vaut "É".
Leur usage le plus important n'est pas cosmétique: c'est la normalisation avant comparaison. Si un utilisateur tape OUI, oui ou Oui, comparer directement à "oui" donne trois réponses différentes; comparer reponse.lower() à "oui" donne la bonne réponse dans les trois cas.
Nettoyer les bords: strip
strip() renvoie la chaîne débarrassée des espaces blancs de début et de fin — espaces, tabulations, retours à la ligne. C'est le premier geste sur toute saisie utilisateur et sur toute ligne lue dans un fichier, parce qu'un retour à la ligne invisible fait échouer une comparaison ou une conversion.
saisie = " 4.5 \n"
propre = saisie.strip()
print("[" + saisie + "]")
print("[" + propre + "]")
print(float(propre) + 1)
print("...Alice...".strip("."))
[ 4.5
]
[4.5]
5.5
Alice
Les crochets ajoutés autour de la valeur rendent visibles les espaces, sans quoi on ne voit rien: le premier affichage est coupé en deux lignes par le retour à la ligne resté dans la saisie. La dernière ligne montre qu'on peut passer un argument à strip: il enlève alors, aux deux bouts, tous les caractères figurant dans la chaîne donnée. Les variantes lstrip() et rstrip() ne nettoient qu'un seul côté.
Remplacer: replace
replace(ancien, nouveau) renvoie une copie où toutes les occurrences de ancien ont été remplacées par nouveau. Un troisième argument facultatif limite le nombre de remplacements.
phrase = "Le cours de math commence a 8h et le cours de math finit a 10h"
print(phrase.replace("math", "physique"))
print(phrase.replace("cours", "COURS", 1))
print(phrase)
Le cours de physique commence a 8h et le cours de physique finit a 10h
Le COURS de math commence a 8h et le cours de math finit a 10h
Le cours de math commence a 8h et le cours de math finit a 10h
La troisième ligne de sortie est la plus instructive: après deux replace, phrase est exactement ce qu'elle était. Les deux nouvelles chaînes ont été affichées puis perdues.
Chercher: find et index
find(sous_chaine) renvoie l'indice où commence la première occurrence, ou −1 s'il n'y en a aucune. index(sous_chaine) fait exactement la même chose, mais lève une ValueError lorsque la recherche échoue. Cette différence de comportement en cas d'échec est tout le sujet.
adresse = "alice.dupont@unil.ch"
print(adresse.find("@"))
print(adresse.find("."))
print(adresse.find("!"))
print(adresse.index("@"))
12
5
-1
12
L'arobase est à l'indice 12; le premier point est à l'indice 5 (celui de alice.dupont, pas celui de unil.ch: find s'arrête à la première occurrence); le point d'exclamation est absent, d'où -1. Voyons maintenant index sur le même caractère absent.
adresse = "alice.dupont@unil.ch"
print(adresse.index("!"))
Traceback (most recent call last):
File "recherche.py", line 2, in <module>
print(adresse.index("!"))
~~~~~~~~~~~~~^^^^^
ValueError: substring not found
Laquelle choisir? find quand l'absence est un cas normal que votre programme doit traiter — c'est le cas d'une adresse électronique possiblement mal saisie, et vous écrirez if position == -1: juste après. index quand l'absence est une anomalie qui doit interrompre le programme plutôt que de le laisser continuer avec une valeur fausse. Le piège classique est d'utiliser find et d'oublier de tester le -1: comme -1 est un indice parfaitement valide (le dernier caractère), la tranche adresse[:position] fabriquée à partir d'un -1 non testé donnera silencieusement toute la chaîne sauf son dernier caractère. Rien n'explosera; le résultat sera simplement faux.
Tester le début, la fin, la présence
startswith(prefixe) et endswith(suffixe) renvoient True ou False. L'opérateur in, lui, teste la présence d'une sous-chaîne n'importe où.
fichier = "rapport_final.pdf"
print(fichier.endswith(".pdf"))
print(fichier.startswith("rapport"))
print(fichier.endswith(".txt"))
phrase = "elle vend des coquillages sur le rivage"
print(phrase.count("e"))
print(phrase.count("es"))
True
True
False
7
2
count(sous_chaine) compte les occurrences sans recouvrement. La phrase contient sept e et deux fois la suite es (dans des et à la fin de coquillages).
mot = "PROGRAMME"
print("GRAM" in mot)
print("gram" in mot)
print("Z" in mot)
print("" in mot)
True
False
False
True
La deuxième ligne est la leçon: in respecte la casse, comme ==, comme find, comme tout le reste. "gram" n'est pas dans "PROGRAMME", et un programme qui cherche un mot dans un texte doit donc mettre les deux en minuscules avant de chercher. La dernière ligne est une curiosité logique: la chaîne vide est contenue dans n'importe quelle chaîne, comme l'ensemble vide est inclus dans tout ensemble.
Découper et recoller: split et join
split() sans argument découpe une chaîne sur les espaces et renvoie une liste de morceaux; avec un argument, il découpe sur ce séparateur. join fait l'inverse: separateur.join(morceaux) recolle des morceaux en intercalant le séparateur.
Le mot «liste» est un type que nous n'étudierons qu'au chapitre 6; ici nous nous en tiendrons aux deux usages qui n'exigent rien de plus qu'un affichage et un len.
phrase = "le chat dort"
print(phrase.split())
print(len(phrase.split()))
print(" ".join(phrase.split()))
print("2026-09-18".split("-"))
print("-".join("PROGRAMME"))
['le', 'chat', 'dort']
3
le chat dort
['2026', '09', '18']
P-R-O-G-R-A-M-M-E
Trois choses à retenir. split() sans argument traite plusieurs espaces consécutifs comme un seul séparateur, ce qui en fait un compteur de mots très correct: len(phrase.split()) donne 3. La combinaison " ".join(phrase.split()) normalise les espaces d'un texte, et c'est un idiome courant. Enfin, "-".join("PROGRAMME") montre que join accepte aussi une chaîne: elle en parcourt les caractères un à un. Nous reviendrons sur split au chapitre 6, quand nous saurons atteindre le deuxième morceau.
Que vaut l'expression , autrement dit la recherche de la lettre z dans la chaîne chat?
Assembler du texte: le formatage
Un programme affiche rarement une chaîne toute faite. Il affiche un mélange: du texte fixe, des valeurs de variables, des résultats de calculs. Comment les assembler?
La concaténation et ses limites
Le plus direct est l'opérateur +, qui colle deux chaînes bout à bout. Mais il exige que les deux opérandes soient des chaînes.
nom = "Alice"
note = 4.5
print("La note de " + nom + " est " + note)
Traceback (most recent call last):
File "concat.py", line 3, in <module>
print("La note de " + nom + " est " + note)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
TypeError: can only concatenate str (not "float") to str
Le message est exact au mot près: «on ne peut concaténer à une chaîne qu'une chaîne, pas un flottant». Le remède immédiat est la conversion explicite avec str.
nom = "Alice"
note = 4.5
print("La note de " + nom + " est " + str(note))
print("-" * 20)
print("ab" * 3)
La note de Alice est 4.5
--------------------
ababab
Au passage, l'opérateur * entre une chaîne et un entier répète la chaîne: "-" * 20 fabrique une ligne de séparation de vingt tirets, ce qui est le moyen le plus court de tracer un trait.
La concaténation marche, mais regardez ce qu'il faut écrire pour une phrase de deux variables: quatre +, deux paires de guillemets contenant des espaces qu'on oublie une fois sur deux, et un str autour de chaque nombre. Pour trois ou quatre valeurs, l'expression devient illisible, et l'erreur la plus fréquente est l'espace manquant avant ou après un morceau.
Les f-strings
Python 3.6 a introduit une notation qui supprime tout cela: la f-string, ou chaîne formatée littérale. On préfixe la chaîne d'un f et l'on écrit les valeurs à insérer directement dans le texte, entre accolades.
nom = "Alice"
note = 4.5
print(f"La note de {nom} est {note}")
print(f"{nom} a {note} et il lui manque {6 - note} points pour le maximum")
print(f"{nom} a la moyenne: {
La note de Alice est 4.5
Alice a 4.5 et il lui manque 1.5 points pour le maximum
Alice a la moyenne: True
Il reste {une accolade} litterale
Quatre observations. La première ligne montre l'essentiel: on lit la phrase telle qu'elle sera affichée, les espaces sont à leur place, et aucun str n'est nécessaire — la conversion est automatique, pour n'importe quel type. La deuxième montre que les accolades peuvent contenir n'importe quelle expression, pas seulement un nom de variable: 6 - note est calculé puis inséré. La troisième insère un booléen. La quatrième répond à la question qui vient immédiatement: pour afficher une accolade littérale, on la double.
Le f doit être là. C'est l'oubli le plus fréquent, et il est silencieux: sans lui, Python affiche le texte tel quel, accolades comprises, sans la moindre erreur. Si votre sortie contient des accolades et des noms de variables, cherchez le f manquant.
Les spécificateurs de format
Après un deux-points à l'intérieur des accolades vient la partie la plus utile du chapitre pour la suite du cours: la description de la présentation de la valeur.
moyenne = 45.5 / 10
print(f"Moyenne brute: {moyenne}")
print(f"Moyenne: {moyenne:.2f}")
print(f"Moyenne: {moyenne:.1f}")
print(f"Un tiers: {1 / 3:.4f}")
print
Moyenne brute: 4.55
Moyenne: 4.55
Moyenne: 4.5
Un tiers: 0.3333
Pourcentage: 80%
Le format .2f signifie «en notation décimale (fixed point), avec exactement deux chiffres après le point». C'est celui dont vous aurez besoin dans presque tous les chapitres suivants: une moyenne, un prix, un temps de calcul s'affichent avec deux décimales, pas avec les dix-sept chiffres que Python imprime sans consigne.
La troisième ligne mérite un arrêt. 4.55 arrondi à une décimale donne 4.5 et non 4.6, alors que la règle scolaire dirait le contraire. Ce n'est pas un bug: comme nous l'avons vu au chapitre 1 avec 0.1 + 0.2, le nombre flottant le plus proche de 4,55 est très légèrement inférieur à 4,55, et l'arrondi le suit fidèlement. Un affichage arrondi n'est pas un arrondi mathématique; quand la valeur exacte compte, ne comptez pas sur .1f pour trancher un cas d'égalité.
La largeur et l'alignement viennent avant la précision: {valeur:<10} occupe dix colonnes alignées à gauche, {valeur:>6} six colonnes alignées à droite, {valeur:^16} seize colonnes centrées. C'est ainsi qu'on fabrique un tableau qui tient debout.
Une f-string affiche le nom Alice cadré à gauche sur dix colonnes, puis la note 4.5 cadrée à droite sur six colonnes avec deux décimales. Combien de caractères la ligne obtenue contient-elle?
Parcourir une chaîne
Beaucoup de traitements de texte consistent à examiner chaque caractère à tour de rôle: compter les voyelles, chiffrer un message, vérifier qu'un mot de passe contient un chiffre. Python offre deux façons de le faire, et savoir laquelle choisir est un vrai savoir-faire.
Parcourir les caractères
La première est directe: une boucle for sur la chaîne donne successivement ses caractères.
mot = "PROG"
for caractere in mot:
print(caractere)
P
R
O
G
La variable de boucle reçoit, au premier tour la chaîne "P", au deuxième "R", et ainsi de suite. Il n'y a pas d'indice, pas de len, pas de risque d'erreur à un près. C'est la forme à préférer par défaut.
Parcourir les indices
La seconde passe par range(len(chaine)) et redonne les indices, dont on tire les caractères.
mot = "PROG"
for i in range(len(mot)):
print(f"indice {i} : {mot[i]}")
indice 0 : P
indice 1 : R
indice 2 : O
indice 3 : G
range(len(mot)) vaut range(4), donc i prend les valeurs 0, 1, 2 et 3 — jamais 4, ce qui est exactement ce qu'il faut. C'est ici que la convention «à partir de zéro» et la borne exclue de range se rencontrent, et il faut bien voir qu'elles s'accordent parfaitement: aucun - 1 n'est nécessaire.
Voici un cas où la position est réellement nécessaire: repérer une lettre doublée, ce qui suppose de comparer chaque caractère avec le suivant.
mot = "PROGRAMME"
# on compare chaque caractere avec le suivant: il faut les indices
for i in range(len(mot) - 1):
if mot[i] == mot[i + 1]:
print(f"lettre doublee : {mot[i]} aux indices {i} et {i + 1}")
lettre doublee : M aux indices 6 et 7
Le - 1 de range(len(mot) - 1) n'est pas une superstition: la boucle utilise mot[i + 1], donc le dernier i acceptable est 7 et non 8. Sans ce - 1, le dernier tour demanderait mot[9] et lèverait une IndexError. Chaque fois que votre boucle regarde le voisin, la borne doit reculer d'autant.
Comparer et ordonner
Les opérateurs de comparaison du chapitre 2 fonctionnent sur les chaînes. == teste l'égalité caractère par caractère, ce qui est sans surprise. Les opérateurs < et > demandent une explication.
print("abricot" < "banane")
print("abricot" < "abricots")
print("Zoe" < "alice")
print("Zoe".lower() < "alice")
print("2" < "10")
print(2 < 10)
True
True
True
False
False
True
Les deux premières lignes sont conformes à l'intuition du dictionnaire. La troisième ne l'est pas: Zoe est déclaré plus petit que alice, alors que z vient après a. La raison est que la comparaison porte sur les codes des caractères, et que dans la table de base toutes les majuscules précèdent toutes les minuscules: Z a le code 90, a le code 97. La quatrième ligne le confirme: une fois les deux chaînes mises en minuscules, l'ordre s'inverse et devient celui qu'on attendait.
Les deux dernières lignes rappellent qu'un chiffre écrit dans une chaîne n'est pas un nombre. "2" < "10" est faux parce que la comparaison commence par 2 contre 1 et s'arrête là, tandis que 2 < 10 est vrai. Si vous triez des numéros de version, des dossiers ou des dates écrits en texte, c'est l'ordre lexicographique que vous obtenez, pas l'ordre numérique. C'est d'ailleurs pourquoi on écrit les dates 2026-09-18 avec les mois et les jours sur deux chiffres: dans ce format, l'ordre lexicographique coïncide avec l'ordre chronologique.
Les accents, et pourquoi trier du français n'est pas simple
print("zebre" < "eleve")
print("elephant" < "eleve")
print("élève" < "zebre")
print(ord("e"), ord("é"), ord("z"))
False
True
False
101 233 122
La deuxième ligne est correcte et la troisième est absurde: élève est déclaré postérieur à zebre. La dernière ligne en donne la cause: le code de é est 233, très au-delà de celui de z qui vaut 122. Toutes les lettres accentuées sont rangées, dans la table Unicode, après l'ensemble des lettres non accentuées.
Il faut le dire honnêtement: < sur des chaînes ne trie pas du français. Un annuaire correct classe «élève» entre «electrique» et «eleveur», traite «Ça» comme «Ca», et sait que dans certaines langues des groupes de lettres se classent comme une lettre unique. C'est la collation, et elle dépend de la langue: le même logiciel doit trier différemment un annuaire suédois et un annuaire français. Python fournit le module locale pour cela, et les vraies bibliothèques de collation implémentent l'algorithme Unicode de collation; c'est hors du programme de ce cours. Ce qui est dans le programme, c'est de savoir que le problème existe, et de ne pas livrer un classement alphabétique obtenu par un simple < en prétendant qu'il est correct.
L'approximation raisonnable à notre niveau, et celle que nous utiliserons dans le dernier programme du chapitre, consiste à normaliser avant de comparer: tout mettre en minuscules et remplacer chaque lettre accentuée par sa version simple. C'est imparfait — cela confond «tache» et «tâche» — mais c'est explicite, et un traitement imparfait qu'on a décidé vaut mieux qu'un traitement faux qu'on subit.
Coder les caractères
ord et chr
Derrière chaque caractère il y a un nombre entier, son point de code (code point). Deux fonctions natives font le passage.
print(ord("A"), ord("a"), ord("0"))
print(chr(65), chr(97), chr(233))
print(chr(ord("A") + 1))
print(ord("a") - ord(
65 97 48
A a é
B
32
ord prend un caractère et renvoie son point de code; chr fait l'inverse. La troisième ligne montre l'usage typique: pour «avancer d'une lettre», on convertit en nombre, on ajoute, on reconvertit. La quatrième révèle une régularité utile: dans la table, chaque minuscule est exactement 32 crans après la majuscule correspondante. Le chiffre 0 a le code 48 et n'est évidemment pas le nombre 0 — c'est la même distinction que dans le chapitre 1, vue de plus près.
Une chaîne est une suite de points de code Unicode
Cette séparation a une conséquence très concrète pour vous.
mot = "éléphant"
print(len(mot))
print(mot[0], mot[1], mot[2])
octets = mot.encode("utf-8")
print(len(octets))
print(octets)
8
é l é
10
b'\xc3\xa9l\xc3\xa9phant'
len(mot) vaut 8, c'est-à-dire le nombre de caractères: é, l, é, p, h, a, n, t. L'indexation suit: mot[0] est bien le é entier, pas un demi-caractère. Mais le même mot encodé en UTF-8 occupe 10 octets, parce que dans cet encodage chaque é en prend deux (\xc3\xa9) tandis que les lettres non accentuées en prennent un. len compte des caractères, pas des octets, et c'est très exactement ce que vous voulez.
Retenez-en une règle pratique: tant que vous restez dans le monde str, les accents ne posent aucun problème d'indexation. Les ennuis commencent aux frontières — lecture d'un fichier, envoi sur un réseau — quand l'encodage utilisé pour écrire n'est pas celui utilisé pour lire. C'est le sujet du chapitre 8; sachez seulement que si un texte s'affiche éléphant, personne n'a corrompu vos données: un fichier UTF-8 a été lu avec un autre encodage.
Deux programmes complets
Compter les voyelles d'une phrase
Le problème: étant donné une phrase, compter combien de ses caractères sont des voyelles. Le plan est celui d'un accumulateur du chapitre 3: un compteur à zéro, un parcours, une condition, une incrémentation. La nouveauté est que ce qu'on parcourt est une chaîne, et que le test d'appartenance se fait avec in sur une chaîne de voyelles.
phrase = "Le petit chat dort sur le tapis du salon"
voyelles = "aeiouy"
compteur = 0
for caractere in phrase.lower():
if caractere in voyelles:
compteur = compteur + 1
print(f"Phrase : {phrase}")
print(f"{len(phrase)} caracteres, dont {compteur} voyelles"
Phrase : Le petit chat dort sur le tapis du salon
40 caracteres, dont 12 voyelles
Proportion de voyelles : 30.0%
Trois décisions méritent d'être commentées. Le phrase.lower() place la normalisation dans la boucle d'entrée plutôt que dans le test: sans lui, il faudrait écrire if caractere in "aeiouyAEIOUY", ce qui marche mais se dégrade dès qu'on ajoute les accents. Le parcours est fait sur les caractères et non sur les indices, conformément à la règle du paragraphe précédent: la position ne sert à rien ici. Enfin, compteur / len(phrase) compte les espaces dans le total, ce qui est un choix: la phrase fait bien 40 caractères, espaces compris.
Voici la trace des treize premiers tours, telle que le programme la produit, sur le début de la phrase. C'est la façon la plus sûre de vérifier une boucle: on la déroule.
| Tour | caractere | voyelle? | compteur après |
|---|---|---|---|
| 0 | l | non | 0 |
| 1 | e | oui | 1 |
| 2 | espace | non | 1 |
| 3 | p | non | 1 |
| 4 | e | oui | 2 |
| 5 | t | non | 2 |
| 6 | i | oui | 3 |
| 7 | t | non | 3 |
| 8 | espace | non | 3 |
| 9 | c | non | 3 |
| 10 | h | non | 3 |
| 11 | a | oui | 4 |
| 12 | t | non | 4 |
Le compteur ne bouge qu'aux tours où la condition est vraie, et il conserve sa valeur aux autres: c'est l'invariant de l'accumulateur, «après le tour , compteur vaut le nombre de voyelles parmi les premiers caractères».
Détecter un palindrome
Un palindrome est un texte qui se lit de la même façon dans les deux sens, une fois qu'on a ignoré les espaces, la ponctuation, la casse et les accents. radar en est un; la phrase «Ésope reste ici et se repose» aussi, mais il faut du travail pour le voir.
Le programme se décompose en deux fonctions. La première réduit un texte à ses seules lettres minuscules non accentuées; la seconde compare cette réduction à son retournement, avec l'idiome [::-1] du début du chapitre.
def lettres_seules(texte):
"""Ne garde que les lettres a-z, en minuscules et sans accents."""
propre = ""
for caractere in sans_accents(texte.lower()):
if caractere in "abcdefghijklmnopqrstuvwxyz":
propre = propre + caractere
return propre
def est_palindrome(texte):
"""Dit si texte se lit de la meme facon dans les deux sens."""
reduit = lettres_seules(texte)
return reduit == reduit[::-1]
esoperesteicietserepose
True
True
False
Le programme complet suppose que la fonction sans_accents de l’exemple 5.3 est définie au-dessus; c'est ainsi qu'il a été exécuté pour produire cette sortie.
Trois points de méthode. D'abord, propre = propre + caractere construit une nouvelle chaîne à chaque tour, ce qui est la seule façon de faire avec un objet immuable; nous verrons au chapitre 6 une manière plus économique. Ensuite, la comparaison finale ne coûte rien à écrire: reduit == reduit[::-1] tient en une expression, là où une boucle comparant le premier au dernier, le deuxième à l'avant-dernier, demanderait une dizaine de lignes et un - 1 bien placé. Enfin, lettres_seules est affichée séparément à la ligne 14: vérifier l'étape intermédiaire est ce qui permet d'affirmer que le True de la ligne suivante est un vrai True et non un accident. Un test qui renvoie True pour la mauvaise raison est le pire des tests.
Il faut être honnête sur ce que ce programme fait: il déclare "tache" et "tâche" équivalents, et il ignore toute ponctuation, y compris les apostrophes et les traits d'union. Ces décisions sont acceptables pour un jeu de mots, mais elles seraient inacceptables pour une recherche dans un catalogue de bibliothèque. La normalisation n'est jamais neutre: elle efface des distinctions, et il faut savoir lesquelles.
Synthèse
- Une chaîne est une suite ordonnée et finie de caractères, écrite entre guillemets simples, doubles ou triples;
\n,\t,\\et\"y insèrent des caractères spéciaux, et le préfixerdésactive cette interprétation. - L'indexation commence à zéro: les indices valides d'une chaîne de longueur vont de 0 à , ou de à depuis la fin, et
len(chaine)n'est jamais un indice valide — c'est l'erreur à un près, qui se manifeste par uneIndexError. - La tranche
chaine[debut:fin:pas]inclut la borne de début et exclut celle de fin; les bornes omises valent les extrémités, une borne hors limites est ramenée au bord sans erreur, etchaine[::-1]retourne la chaîne. - Une chaîne est immuable:
chaine[0] = "A"lève uneTypeError, et toutes les méthodes (upper,strip,replace, …) renvoient une nouvelle chaîne sans modifier l'originale — d'où la règlenom = nom.upper(). findsignale un échec par-1etindexpar uneValueError: on choisit selon que l'absence est un cas normal ou une anomalie, et on n'oublie jamais de tester le-1.- Une f-string insère la valeur de n'importe quelle expression entre accolades, avec un format facultatif:
:.2fpour deux décimales,:<10et:>6pour la largeur et l'alignement. C'est la façon normale d'assembler un affichage en Python.
Que vaut l'expression , où bonjour est une chaîne de sept lettres minuscules?
Exercices
Vous pouvez afficher le corrigé directement sous chaque énoncé après avoir cherché la solution.
Le mot le plus long du français courant est anticonstitutionnellement. Écrivez un programme qui affiche, chacun sur sa ligne et en utilisant des f-strings: sa longueur, son premier caractère, son dernier caractère, ses cinq premiers caractères, ses cinq derniers caractères, le mot à l'envers, et le nombre de n qu'il contient. N'écrivez aucun nombre à la main à part le 5.
Solution
mot = "anticonstitutionnellement"
print(f"Longueur : {len(mot)}")
print(f"Premier caractere : {mot[0]}")
Écrivez un programme qui, à partir de la variable adresse valant "alice.dupont@unil.ch", affiche séparément l'identifiant (ce qui précède l'arobase), le domaine (ce qui la suit) et la longueur du domaine. Votre programme doit afficher Adresse invalide : pas de @ si l'adresse ne contient pas d'arobase, et vous testerez ce cas en changeant la valeur de la variable.
Solution
adresse = "alice.dupont@unil.ch"
position = adresse.find("@")
if position == -1:
print("Adresse invalide : pas de @")
else:
identifiant = adresse[:position]
La variable nom_complet vaut "alice dupont", en minuscules, avec un seul espace. Écrivez un programme qui affiche le prénom avec une majuscule initiale, le nom de famille tout en majuscules, les initiales sous la forme A.D., et enfin la ligne d'annuaire DUPONT Alice. N'utilisez pas split.
Solution
nom_complet = "alice dupont"
espace = nom_complet.find(" ")
prenom = nom_complet[:espace]
famille = nom_complet[espace + 1:]
initiales = prenom[0].upper() + "." +
Le chiffre de César décale chaque lettre d'un nombre fixe de rangs dans l'alphabet, en revenant au début après z: avec un décalage de 3, a devient d, b devient e, et z devient c. Écrivez une fonction cesar(texte, decalage) qui renvoie le texte chiffré, en laissant inchangé tout caractère qui n'est pas une lettre minuscule non accentuée. Vérifiez que déchiffrer avec le décalage opposé redonne le message d'origine.
Solution
def cesar(texte, decalage):
"""Decale chaque lettre minuscule de decalage rangs, en bouclant sur z."""
resultat = ""
for caractere in texte:
if caractere in "abcdefghijklmnopqrstuvwxyz":
rang = ord(caractere)
Écrivez un programme qui trouve le mot le plus long d'une phrase, sans utiliser split: parcourez la phrase caractère par caractère, accumulez le mot en cours, et comparez sa longueur à celle du meilleur mot trouvé jusque-là chaque fois que vous rencontrez un espace. Testez-le sur "le petit chat dort sur le tapis du salon" et expliquez ce qui se passe lorsque deux mots ont la même longueur.
Solution
phrase = "le petit chat dort sur le tapis du salon"
mot_courant = ""
mot_le_plus_long = ""
for caractere in phrase + " ": # l'espace final ferme le dernier mot
if caractere == " ":
if
Références
- Downey, Think Python, 2e édition, O'Reilly — chapitre 8, «Strings»: indexation, tranches, parcours et méthodes, avec les mêmes conventions que celles employées ici.
- Swinnen, Apprendre à programmer avec Python 3, Eyrolles — chapitre sur les chaînes de caractères, en français, avec de nombreux exercices de manipulation.
- Matthes, Python Crash Course, 3e édition, No Starch Press — chapitre 2, «Variables and Simple Data Types», pour les f-strings et le nettoyage des espaces.
- Documentation officielle Python, The Python Standard Library, section «Text Sequence Type — str»: la liste complète et faisant foi des méthodes de chaîne.
- Documentation officielle Python, The Python Language Reference, section «Formatted string literals» et Library, «Format Specification Mini-Language»: la grammaire exacte des f-strings et des spécificateurs de format.
- Documentation officielle Python, HOWTO, «Unicode HOWTO»: la distinction entre points de code et octets, et ce qu'implique l'encodage UTF-8.