Chapitre 06 — Chaînes de caractères et compréhensions¶
Durée estimée : 5 à 6 heures (cours, exercices et mini-projet)
Prérequis : Chapitre 05 — Les fonctions (
def, paramètres,return)Objectifs — à la fin de ce chapitre, tu sauras…
- manipuler du texte avec les principales méthodes de
str(split,join,strip,replace,find…) ;- extraire des morceaux de chaîne avec le slicing ;
- écrire des compréhensions de listes, de dictionnaires et d'ensembles ;
- parcourir plus élégamment avec
enumerateetzip;- trier finement avec
sorted(..., key=...)et des fonctionslambda;- nettoyer et analyser un texte complet.
Cours¶
1. Rappels : une chaîne est une séquence¶
Tu manipules des chaînes (str) depuis le chapitre 01. Il est temps de voir qu'une chaîne se comporte comme une liste de caractères (mais une liste figée, comme un tuple).
mot = "Python"
print(len(mot)) # → 6
print(mot[0]) # → P
print(mot[-1]) # → n
print("y" in mot) # → True
print("thon" in mot) # → True
for lettre in mot:
print(lettre, end=" ")
print()
in sur une chaîne cherche une sous-chaîne entière : "thon" in "Python" est vrai.
Les chaînes sont immuables¶
Comme les tuples, on ne peut pas modifier une chaîne « sur place » :
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: 'str' object does not support item assignment
Toutes les « modifications » de chaîne créent en fait une nouvelle chaîne :
C'est fondamental pour comprendre les méthodes qui suivent : aucune ne modifie la chaîne d'origine, elles renvoient une nouvelle chaîne. Il faut donc récupérer le résultat.
nom = "alice"
nom.upper() # le résultat est perdu !
print(nom) # → alice
nom = nom.upper()
print(nom) # → ALICE
Caractères spéciaux et chaînes sur plusieurs lignes¶
print("Ligne 1\nLigne 2") # \n = retour à la ligne
print("Col1\tCol2") # \t = tabulation
print("Il a dit \"bonjour\"") # \" = guillemet dans la chaîne
print('C\'est facile') # \' = apostrophe
print("C:\\Users\\alice") # \\ = un antislash
Les triples guillemets permettent d'écrire du texte sur plusieurs lignes :
poeme = """Mignonne, allons voir si la rose
Qui ce matin avait déclose
Sa robe de pourpre au soleil"""
print(len(poeme.splitlines())) # → 3
2. Le slicing en détail¶
Tu as découvert le slicing sur les listes au chapitre 04. Il fonctionne exactement pareil sur les chaînes : chaine[debut:fin:pas], avec fin exclu.
indice positif : 0 1 2 3 4 5 6 7 8 9
caractère : B o n j o u r ! ! !
indice négatif : -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
texte = "Bonjour!!!"
print(texte[0:3]) # → Bon
print(texte[3:7]) # → jour
print(texte[:7]) # → Bonjour
print(texte[7:]) # → !!!
print(texte[-3:]) # → !!!
print(texte[:-3]) # → Bonjour
print(texte[::2]) # → Bnor!
print(texte[::-1]) # → !!!ruojnoB
Astuce pour retenir : imagine que les indices désignent les séparations entre les caractères, et non les caractères eux-mêmes. texte[3:7] prend ce qui se trouve entre la séparation 3 et la séparation 7.
Contrairement à l'accès par indice, le slicing ne provoque jamais d'erreur si on dépasse :
Quelques usages typiques :
fichier = "rapport_2024.pdf"
print(fichier[-4:]) # → .pdf (l'extension)
print(fichier[:-4]) # → rapport_2024 (sans l'extension)
code_postal = "75011"
print(code_postal[:2]) # → 75 (le département)
mot = "kayak"
print(mot == mot[::-1]) # → True (palindrome en une ligne !)
3. Les méthodes de chaîne essentielles¶
Une méthode s'appelle avec un point : chaine.methode(...). Voici celles que tu utiliseras tout le temps, regroupées par usage.
Changer la casse¶
titre = "le Petit PRINCE"
print(titre.upper()) # → LE PETIT PRINCE
print(titre.lower()) # → le petit prince
print(titre.capitalize()) # → Le petit prince
print(titre.title()) # → Le Petit Prince
print(titre.swapcase()) # → LE pETIT prince
Pour comparer deux textes sans tenir compte des majuscules, on les met tous les deux en minuscules :
Nettoyer les espaces¶
strip() retire les espaces (et retours à la ligne, tabulations) au début et à la fin. C'est indispensable pour nettoyer une saisie utilisateur.
lstrip() et rstrip() ne nettoient qu'un seul côté (gauche ou droite) :
saisie = " bonjour "
print(f"[{saisie.strip()}]") # → [bonjour]
print(f"[{saisie.lstrip()}]") # → [bonjour ]
print(f"[{saisie.rstrip()}]") # → [ bonjour]
print("---titre---".strip("-")) # → titre
On peut passer à strip les caractères à retirer, comme dans la dernière ligne.
Chercher¶
phrase = "Le chat dort. Le chat mange."
print(phrase.find("chat")) # → 3 (indice de la 1re occurrence)
print(phrase.find("chien")) # → -1 (pas trouvé)
print(phrase.count("chat")) # → 2
print(phrase.startswith("Le")) # → True
print(phrase.endswith(".")) # → True
print(phrase.index("dort")) # → 8
find renvoie -1 si rien n'est trouvé, alors que index provoque une erreur ValueError: substring not found. Pour un simple test de présence, in reste le plus lisible.
Remplacer¶
phrase = "J'aime le thé. Le thé c'est bon."
print(phrase.replace("thé", "café")) # → J'aime le café. Le café c'est bon.
print(phrase.replace("thé", "café", 1)) # → J'aime le café. Le thé c'est bon.
print(phrase.replace(".", "")) # → J'aime le thé Le thé c'est bon
Le troisième argument limite le nombre de remplacements. Remplacer par "" revient à supprimer.
Tester le contenu¶
Ces méthodes renvoient un booléen :
print("2024".isdigit()) # → True (que des chiffres)
print("12.5".isdigit()) # → False (le point n'est pas un chiffre)
print("Bonjour".isalpha()) # → True (que des lettres)
print("Été".isalpha()) # → True (les accents sont des lettres)
print("abc123".isalnum()) # → True (lettres ou chiffres)
print(" ".isspace()) # → True
print("ALICE".isupper()) # → True
print("alice".islower()) # → True
Découper avec split et assembler avec join¶
Ce sont les deux méthodes les plus importantes du chapitre.
split() découpe une chaîne en liste :
phrase = "le chat dort"
print(phrase.split()) # → ['le', 'chat', 'dort']
date = "14/07/1789"
print(date.split("/")) # → ['14', '07', '1789']
ligne_csv = "Alice;30;Lyon"
nom, age, ville = ligne_csv.split(";")
print(nom, age, ville) # → Alice 30 Lyon
print("a,b,,c".split(",")) # → ['a', 'b', '', 'c']
Sans argument, split() coupe sur n'importe quel blanc (espaces multiples, tabulations, retours à la ligne) et ignore les blancs en trop. Avec un séparateur, il coupe exactement sur ce séparateur, d'où la chaîne vide '' dans le dernier exemple.
join() fait l'inverse : il assemble une liste de chaînes en une seule, en insérant un séparateur. Attention à la syntaxe, c'est le séparateur qui appelle la méthode :
mots = ["le", "chat", "dort"]
print(" ".join(mots)) # → le chat dort
print("-".join(mots)) # → le-chat-dort
print("".join(mots)) # → lechatdort
print(", ".join(mots)) # → le, chat, dort
print("\n".join(mots))
join n'accepte que des chaînes :
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: sequence item 0: expected str instance, int found
Il faut d'abord convertir chaque nombre en chaîne ; on verra la façon élégante de le faire avec les compréhensions (section 6).
Combinés, split et join permettent de transformer un texte en quelques lignes :
phrase = " python est super "
propre = " ".join(phrase.split())
print(f"[{propre}]") # → [python est super]
Aligner et remplir¶
print("chat".center(12, "*")) # → ****chat****
print("chat".ljust(8) + "|") # → chat |
print("chat".rjust(8) + "|") # → chat|
print("7".zfill(3)) # → 007
4. Les f-strings, en plus fin¶
Tu connais les f-strings depuis le chapitre 01. Après les deux-points, on peut ajouter un format très précis :
prix = 1234.5678
print(f"{prix:.2f}") # → 1234.57 (2 décimales)
print(f"{prix:,.2f}") # → 1,234.57 (séparateur de milliers)
print(f"{0.256:.1%}") # → 25.6% (pourcentage)
print(f"{42:05d}") # → 00042 (5 chiffres, complété par des 0)
nom = "Alice"
print(f"[{nom:<10}]") # → [Alice ] (aligné à gauche sur 10)
print(f"[{nom:>10}]") # → [ Alice] (aligné à droite)
print(f"[{nom:^10}]") # → [ Alice ] (centré)
print(f"[{nom:*^11}]") # → [***Alice***] (centré, rempli de *)
C'est idéal pour afficher des tableaux bien alignés :
produits = [("Pomme", 0.5, 12), ("Ananas", 2.95, 2), ("Kiwi", 0.35, 30)]
print(f"{'Produit':<10}{'Prix':>8}{'Qté':>6}")
print("-" * 24)
for nom, prix, quantite in produits:
print(f"{nom:<10}{prix:>8.2f}{quantite:>6}")
Une autre astuce pratique pour déboguer : = après le nom de variable affiche le nom et la valeur.
5. enumerate, zip et sorted¶
Ces trois fonctions intégrées rendent les boucles beaucoup plus élégantes.
enumerate : l'indice et la valeur¶
Au chapitre 04, pour avoir l'indice, on écrivait for i in range(len(liste)). enumerate fait mieux : il fournit à chaque tour un tuple (indice, valeur), qu'on déballe.
Le paramètre start permet de commencer à compter ailleurs qu'à 0 (pratique pour l'affichage humain) :
podium = ["Alice", "Bob", "Chloé"]
for rang, nom in enumerate(podium, start=1):
print(f"{rang}. {nom}")
Ça marche aussi sur une chaîne :
zip : parcourir plusieurs listes en parallèle¶
zip associe les éléments de plusieurs collections deux à deux (comme une fermeture éclair, zip en anglais).
prenoms = ["Alice", "Bob", "Chloé"]
ages = [30, 25, 35]
villes = ["Lyon", "Paris", "Nice"]
for prenom, age, ville in zip(prenoms, ages, villes):
print(f"{prenom}, {age} ans, habite à {ville}")
Si les listes n'ont pas la même longueur, zip s'arrête à la plus courte. Pour transformer deux listes en dictionnaire, zip est parfait :
cles = ["nom", "age", "ville"]
valeurs = ["Alice", 30, "Lyon"]
fiche = dict(zip(cles, valeurs))
print(fiche) # → {'nom': 'Alice', 'age': 30, 'ville': 'Lyon'}
Et pour comparer des éléments voisins d'une liste, on peut « zipper » la liste avec elle-même décalée :
temperatures = [12, 15, 14, 18, 21]
for avant, apres in zip(temperatures, temperatures[1:]):
print(f"{avant} → {apres} : {apres - avant:+d}")
(Le format +d affiche toujours le signe.)
sorted avec key¶
Tu sais déjà que sorted(liste) renvoie une copie triée et que reverse=True inverse l'ordre. Le paramètre key permet de dire selon quel critère trier : on lui passe une fonction, que sorted appelle sur chaque élément ; le tri se fait sur les résultats.
mots = ["kiwi", "Banane", "abricot", "Cerise", "fraise"]
print(sorted(mots)) # → ['Banane', 'Cerise', 'abricot', 'fraise', 'kiwi']
print(sorted(mots, key=str.lower)) # → ['abricot', 'Banane', 'Cerise', 'fraise', 'kiwi']
print(sorted(mots, key=len)) # → ['kiwi', 'Banane', 'Cerise', 'fraise', 'abricot']
- Par défaut, les majuscules passent avant les minuscules (
"B" < "a"). key=str.lowercompare les versions en minuscules : ordre alphabétique « naturel ».key=lentrie par longueur ; à longueur égale, l'ordre d'origine est conservé (le tri est dit stable).
On peut passer n'importe quelle fonction, y compris une des tiennes :
def derniere_lettre(mot):
return mot[-1]
mots = ["chat", "souris", "lapin", "zebre"]
print(sorted(mots, key=derniere_lettre)) # → ['zebre', 'lapin', 'souris', 'chat']
Les fonctions min et max acceptent aussi key :
6. Les fonctions lambda¶
Écrire une fonction complète avec def juste pour la passer à sorted est un peu lourd. Une lambda est une petite fonction anonyme, écrite en une seule expression :
# Ces deux écritures sont équivalentes :
def double(x):
return x * 2
double_lambda = lambda x: x * 2
print(double(5)) # → 10
print(double_lambda(5)) # → 10
La syntaxe est lambda parametres: expression. Pas de return : la valeur de l'expression est automatiquement renvoyée. Une lambda ne peut contenir qu'une seule expression (pas de if sur plusieurs lignes, pas de boucle).
On ne range pas normalement une lambda dans une variable comme ci-dessus (autant utiliser
def). Une lambda s'utilise directement là où on a besoin d'une fonction, typiquement comme argumentkey.
eleves = [("Alice", 15), ("Bob", 12), ("Chloé", 18), ("David", 12)]
# Trier par note (le 2e élément du tuple)
print(sorted(eleves, key=lambda eleve: eleve[1]))
# Trier par note décroissante
print(sorted(eleves, key=lambda eleve: eleve[1], reverse=True))
# Meilleur élève
print(max(eleves, key=lambda eleve: eleve[1]))
[('Bob', 12), ('David', 12), ('Alice', 15), ('Chloé', 18)]
[('Chloé', 18), ('Alice', 15), ('Bob', 12), ('David', 12)]
('Chloé', 18)
Avec une liste de dictionnaires :
contacts = [
{"nom": "Durand", "age": 42},
{"nom": "Martin", "age": 25},
{"nom": "Bernard", "age": 33},
]
for contact in sorted(contacts, key=lambda c: c["age"]):
print(contact["nom"], contact["age"])
Trier sur plusieurs critères¶
La lambda peut renvoyer un tuple : Python trie d'abord selon le premier élément, puis le deuxième en cas d'égalité. Pour inverser un critère numérique, on utilise le signe moins :
eleves = [("Alice", 15), ("Bob", 12), ("Chloé", 18), ("David", 12), ("Emma", 15)]
# Note décroissante, puis nom alphabétique
classement = sorted(eleves, key=lambda e: (-e[1], e[0]))
print(classement)
Compare avec l'exercice 8 du chapitre 04 : c'est la même idée, en une ligne.
Trier un dictionnaire par valeur¶
scores = {"Léo": 1200, "Maya": 1850, "Sam": 900}
for nom, score in sorted(scores.items(), key=lambda paire: paire[1], reverse=True):
print(nom, score)
scores.items() fournit des tuples (nom, score) ; on trie selon paire[1], le score.
7. Les compréhensions de liste¶
Tu as souvent écrit ce schéma :
nombres = [1, 2, 3, 4, 5]
carres = []
for n in nombres:
carres.append(n * n)
print(carres) # → [1, 4, 9, 16, 25]
Python propose une syntaxe compacte pour exactement ça, la compréhension de liste :
Lis-la de gauche à droite : « la liste des n * n pour chaque n dans nombres ». C'est très proche de la notation mathématique { n² | n ∈ nombres }.
[ n * n for n in nombres ]
───┬── ────────┬───────
│ └─ la boucle : d'où viennent les éléments
└─ l'expression : ce qu'on met dans la nouvelle liste
Exemples :
prenoms = ["alice", "bob", "chloé"]
print([p.capitalize() for p in prenoms]) # → ['Alice', 'Bob', 'Chloé']
print([len(p) for p in prenoms]) # → [5, 3, 5]
print([i * 10 for i in range(5)]) # → [0, 10, 20, 30, 40]
nombres = [1, 2, 3]
print("-".join([str(n) for n in nombres])) # → 1-2-3
prix_ht = [10, 25.5, 100]
print([round(p * 1.2, 2) for p in prix_ht]) # → [12.0, 30.6, 120.0]
Note l'exemple avec join : la compréhension convertit chaque nombre en chaîne, ce qui résout l'erreur vue en section 3.
Filtrer avec if¶
On peut ajouter une condition à la fin pour ne garder que certains éléments :
notes = [8, 15, 12, 6, 19, 10]
print([n for n in notes if n >= 10]) # → [15, 12, 19, 10]
mots = ["Python", "est", "un", "langage", "génial"]
print([m for m in mots if len(m) > 3]) # → ['Python', 'langage', 'génial']
print([m.upper() for m in mots if "a" in m]) # → ['LANGAGE', 'GÉNIAL']
Correspondance avec la boucle classique :
# Compréhension :
resultat = [m.upper() for m in ["chat", "lion", "rat"] if "a" in m]
# Équivaut exactement à :
resultat2 = []
for m in ["chat", "lion", "rat"]:
if "a" in m:
resultat2.append(m.upper())
print(resultat == resultat2) # → True
Transformer avec if/else¶
Attention, différence importante : si tu veux transformer chaque élément selon une condition (et non filtrer), le if ... else se place avant le for. C'est une expression conditionnelle (vue au chapitre 05).
notes = [8, 15, 12, 6]
print(["admis" if n >= 10 else "recalé" for n in notes]) # → ['recalé', 'admis', 'admis', 'recalé']
Pour bien retenir la différence entre les deux formes, voici leur schéma.
[ expression for x in liste if condition ] ← FILTRE : moins d'éléments
[ valeur_si_vrai if condition else valeur_si_faux for x in liste ] ← TRANSFORME : autant d'éléments
Compréhensions imbriquées¶
On peut enchaîner plusieurs for, dans le même ordre que des boucles imbriquées :
couleurs = ["rouge", "bleu"]
tailles = ["S", "M"]
print([f"{c}-{t}" for c in couleurs for t in tailles])
# → ['rouge-S', 'rouge-M', 'bleu-S', 'bleu-M']
grille = [[1, 2, 3], [4, 5, 6]]
print([valeur for ligne in grille for valeur in ligne]) # aplatir
# → [1, 2, 3, 4, 5, 6]
print([[0] * 3 for _ in range(2)]) # grille 2x3 correcte (cf. piège 8 du chapitre 04)
# → [[0, 0, 0], [0, 0, 0]]
Conseil : une compréhension doit rester lisible en un coup d'œil. Si elle dépasse une ligne ou contient plus de deux for, une boucle classique est souvent plus claire.
8. Compréhensions de dictionnaire et d'ensemble¶
Même principe, avec des accolades.
Compréhension de dictionnaire¶
On écrit {cle: valeur for ...} :
mots = ["chat", "éléphant", "rat"]
longueurs = {mot: len(mot) for mot in mots}
print(longueurs) # → {'chat': 4, 'éléphant': 8, 'rat': 3}
prix = {"pain": 1.2, "lait": 0.95, "caviar": 89.0}
print({article: p for article, p in prix.items() if p < 10})
# → {'pain': 1.2, 'lait': 0.95}
print({article: round(p * 2, 2) for article, p in prix.items()})
# → {'pain': 2.4, 'lait': 1.9, 'caviar': 178.0}
# Inverser un dictionnaire (clés ↔ valeurs)
codes = {"FR": "France", "IT": "Italie"}
print({pays: code for code, pays in codes.items()})
# → {'France': 'FR', 'Italie': 'IT'}
{'chat': 4, 'éléphant': 8, 'rat': 3}
{'pain': 1.2, 'lait': 0.95}
{'pain': 2.4, 'lait': 1.9, 'caviar': 178.0}
{'France': 'FR', 'Italie': 'IT'}
Compréhension d'ensemble¶
{expression for ...} sans deux-points crée un ensemble (donc sans doublons) :
mots = ["Chat", "chat", "CHIEN", "chien", "Lapin"]
print(sorted({m.lower() for m in mots})) # → ['chat', 'chien', 'lapin']
phrase = "le chat et le rat"
print(sorted({len(mot) for mot in phrase.split()})) # → [2, 3, 4]
Et les parenthèses ?¶
(x for x in ...) ne crée pas un tuple, mais un générateur : un objet qui produit les valeurs une par une, sans tout stocker en mémoire. On l'utilise surtout directement dans une fonction comme sum, max, any ou all (les parenthèses de la fonction suffisent) :
nombres = [3, 8, 12, 5]
print(sum(n * n for n in nombres)) # → 242
print(any(n > 10 for n in nombres)) # → True (au moins un ?)
print(all(n > 0 for n in nombres)) # → True (tous ?)
print(max(len(m) for m in ["a", "abc", "ab"])) # → 3
any renvoie True si au moins un élément est vrai, all si tous le sont. Ils remplacent élégamment les boucles avec drapeau du chapitre 05.
9. Mettre tout ensemble : nettoyer un texte¶
Voici un exemple réaliste qui combine tout le chapitre : extraire les mots d'un texte, sans ponctuation ni majuscules, et trouver les plus fréquents.
def nettoyer(texte):
"""Renvoie le texte en minuscules, la ponctuation remplacée par des espaces."""
texte = texte.lower()
for signe in ".,;:!?'\"()-":
texte = texte.replace(signe, " ")
return texte
def compter_mots(texte):
"""Renvoie un dictionnaire {mot: nombre d'apparitions}."""
compteur = {}
for mot in nettoyer(texte).split():
compteur[mot] = compteur.get(mot, 0) + 1
return compteur
texte = """Le chat dort. Le chien aboie ! Le chat se réveille,
et le chien, lui, s'endort. C'est la vie du chat."""
compteur = compter_mots(texte)
plus_frequents = sorted(compteur.items(), key=lambda paire: (-paire[1], paire[0]))
for rang, (mot, nombre) in enumerate(plus_frequents[:3], start=1):
print(f"{rang}. {mot:<8} {nombre}")
Remarque le for rang, (mot, nombre) in enumerate(...) : enumerate fournit (rang, (mot, nombre)), et on déballe les deux niveaux d'un coup grâce aux parenthèses.
Pièges fréquents¶
Piège 1 — Oublier que les chaînes sont immuables¶
Les méthodes ont renvoyé de nouvelles chaînes… qui ont été perdues. Correction : récupérer le résultat, éventuellement en enchaînant les méthodes.
Piège 2 — Écrire join à l'envers¶
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'list' object has no attribute 'join'
Correction : c'est le séparateur qui appelle join : "-".join(mots).
Piège 3 — join sur des nombres¶
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found
Correction : convertir d'abord : ", ".join(str(n) for n in [1, 2, 3]).
Piège 4 — split() contre split(" ")¶
texte = "un deux trois"
print(texte.split(" ")) # → ['un', '', 'deux', '', '', 'trois']
print(texte.split()) # → ['un', 'deux', 'trois']
Avec " ", chaque espace est un séparateur, ce qui crée des chaînes vides entre deux espaces consécutifs. Correction : pour découper en mots, utilise split() sans argument.
Piège 5 — Confondre filtre et transformation dans une compréhension¶
Un if placé après le for est un filtre et n'accepte pas de else. Correction : pour transformer, place le if/else avant le for.
Piège 6 — Comparer des textes sans normaliser¶
L'utilisateur a tapé une majuscule et un espace. Correction : if reponse.strip().lower() == "oui":.
Piège 7 — Lambda avec un return ou plusieurs instructions¶
Une lambda contient une expression, pas d'instruction : pas de return, pas d'affectation, pas de boucle. Correction : lambda x: x * x, ou une vraie fonction def si la logique est plus complexe.
Piège 8 — Utiliser une variable de boucle après une compréhension¶
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'n' is not defined
La variable d'une compréhension est locale à la compréhension, contrairement à celle d'une boucle for classique. C'est plutôt une bonne chose : elle n'écrase pas une variable existante.
Exercices¶
Exercice 1 — Manipulations de base ⭐¶
Pour email = " Jean.Dupont@Exemple.FR ", écris le code qui :
1. nettoie les espaces et met tout en minuscules ;
2. extrait la partie avant @ (l'identifiant) et après (le domaine) avec split ;
3. extrait le prénom et le nom de l'identifiant (séparés par un point) et les affiche avec une majuscule ;
4. affiche les initiales en majuscules.
Sortie attendue :
Email : jean.dupont@exemple.fr
Identifiant : jean.dupont
Domaine : exemple.fr
Nom complet : Jean Dupont
Initiales : J.D.
Exercice 2 — Compréhensions simples ⭐¶
Avec des compréhensions (une ligne chacune), à partir de nombres = [3, -1, 8, 0, -5, 12, 7] et mots = ["Soleil", "lune", "Étoile", "ciel", "Nuage"] :
1. la liste des nombres positifs strictement ;
2. la liste des carrés de tous les nombres ;
3. la liste où chaque négatif est remplacé par 0 ;
4. la liste des mots commençant par une majuscule ;
5. un dictionnaire {mot en minuscules: longueur}.
Sortie attendue :
[3, 8, 12, 7]
[9, 1, 64, 0, 25, 144, 49]
[3, 0, 8, 0, 0, 12, 7]
['Soleil', 'Étoile', 'Nuage']
{'soleil': 6, 'lune': 4, 'étoile': 6, 'ciel': 4, 'nuage': 5}
Exercice 3 — Le chiffrement de César ⭐⭐¶
Écris une fonction cesar(texte, decalage) qui décale chaque lettre minuscule non accentuée (a-z) de decalage positions dans l'alphabet (en revenant au début après z) et laisse les autres caractères inchangés. Écris aussi decoder(texte, decalage).
Indice : alphabet = "abcdefghijklmnopqrstuvwxyz", alphabet.index(lettre) donne la position, et % 26 permet de « boucler ».
secret = cesar("rendez-vous a midi !", 3)
print(secret)
print(decoder(secret, 3))
print(cesar("xyz", 2))
Exercice 4 — Tableau de notes aligné ⭐⭐¶
À partir de deux listes, eleves = ["Alice", "Bob", "Chloé", "Dominique"] et notes = [15.5, 9, 12.25, 17], affiche un tableau aligné avec un rang (enumerate), le nom (aligné à gauche sur 11 caractères), la note (à droite, 2 décimales) et « oui »/« non » pour l'admission (note ≥ 10). Termine par la moyenne.
Sortie attendue :
# Élève Note Admis
1 Alice 15.50 oui
2 Bob 9.00 non
3 Chloé 12.25 oui
4 Dominique 17.00 oui
Moyenne : 13.44
Exercice 5 — Trier des produits ⭐⭐¶
On donne :
produits = [
{"nom": "Clavier", "prix": 49.9, "stock": 12},
{"nom": "souris", "prix": 19.9, "stock": 0},
{"nom": "Écran", "prix": 189.0, "stock": 4},
{"nom": "casque", "prix": 49.9, "stock": 7},
]
Avec sorted/min/max et des lambdas, affiche :
1. les noms triés par prix croissant, puis par nom (insensible à la casse) en cas d'égalité ;
2. le nom du produit le plus cher ;
3. les noms des produits en stock, triés alphabétiquement sans tenir compte de la casse ;
4. la valeur totale du stock (somme de prix × stock), arrondie à 2 décimales.
Sortie attendue :
Exercice 6 — Acronymes et titres ⭐⭐¶
Écris deux fonctions :
- acronyme(expression) qui renvoie les initiales en majuscules des mots, en ignorant les petits mots le, la, les, de, des, du, et, en, un, une ("société nationale des chemins de fer" → "SNCF") ;
- titre_propre(texte) qui met une majuscule au début de chaque mot sauf pour les petits mots le, la, les, de, des, du, et, en, un, une (sauf s'il s'agit du premier mot), et réduit les espaces multiples à un seul.
print(acronyme("société nationale des chemins de fer"))
print(acronyme("organisation des nations unies"))
print(titre_propre(" le seigneur des anneaux et la communauté de l'anneau "))
Exercice 7 — Anagrammes ⭐⭐⭐¶
Deux mots sont des anagrammes s'ils contiennent exactement les mêmes lettres (« chien » et « niche »). Écris :
- signature(mot) : renvoie les lettres du mot en minuscules, triées, sous forme de chaîne ("Niche" → "cehin") ;
- sont_anagrammes(mot1, mot2) ;
- grouper_anagrammes(mots) : renvoie la liste des groupes (listes) d'au moins 2 mots qui sont anagrammes entre eux, chaque groupe trié, et les groupes triés par leur premier mot.
print(signature("Niche"))
print(sont_anagrammes("chien", "Niche"))
print(sont_anagrammes("chat", "tache"))
mots = ["chien", "niche", "chine", "rage", "gare", "chat", "ironique", "onirique", "Agre"]
print(grouper_anagrammes(mots))
Exercice 8 — Validateur de plaques d'immatriculation ⭐⭐⭐¶
Une plaque française (système SIV) a le format AB-123-CD : 2 lettres majuscules, tiret, 3 chiffres, tiret, 2 lettres majuscules. Écris une fonction normaliser_plaque(saisie) qui :
- retire les espaces autour, met en majuscules ;
- accepte aussi les saisies sans tirets (ab123cd) ou avec des espaces à la place des tirets (AB 123 CD) ;
- renvoie la plaque au format AB-123-CD si elle est valide, ou None sinon.
Indice : supprime d'abord tirets et espaces, vérifie qu'il reste 7 caractères, puis vérifie chaque morceau avec le slicing, isalpha() et isdigit().
for saisie in ["AB-123-CD", "ab123cd", " xy 987 zt ", "A1-234-BC", "AB-12-CD", "ÉÉ-123-AA"]:
print(f"{saisie!r:15} → {normaliser_plaque(saisie)}")
'AB-123-CD' → AB-123-CD
'ab123cd' → AB-123-CD
' xy 987 zt ' → XY-987-ZT
'A1-234-BC' → None
'AB-12-CD' → None
'ÉÉ-123-AA' → None
({saisie!r} affiche la chaîne avec ses guillemets, ce qui rend les espaces visibles.)
Mini-projet — L'analyseur de texte¶
Cahier des charges¶
Écris un programme qui analyse un texte et produit un rapport. Le texte est stocké dans une variable (au chapitre 07, tu pourras le lire depuis un fichier). Le rapport doit contenir :
- le nombre de caractères (total et hors espaces) ;
- le nombre de mots, de mots différents et de phrases (une phrase se termine par
.,!ou?) ; - la longueur moyenne des mots ;
- le mot le plus long ;
- les 5 mots les plus fréquents parmi les mots d'au moins 4 lettres (pour éviter « le », « de »…), avec une barre de
#proportionnelle ; - les lettres les plus fréquentes (top 5), en ignorant les accents de base (é, è, ê → e ; à → a ; ç → c…).
Contraintes : structure en fonctions avec docstrings, utilisation de compréhensions, sorted avec key, enumerate ; affichage aligné grâce aux f-strings.
Étapes suggérées¶
normaliser(texte): minuscules + remplacement des lettres accentuées (un dictionnaire{"é": "e", ...}et une boucle dereplace).extraire_mots(texte): remplace la ponctuation par des espaces puissplit(). Garde l'apostrophe comme séparateur (« l'analyse » → « l », « analyse »).compter_phrases(texte): compte les.,!et?(attention aux...ou?!: tu peux d'abord remplacer...par., ou compter les morceaux non vides après découpage).frequences(elements): renvoie un dictionnaire de comptage.top(compteur, n): renvoie lesnpaires les plus fréquentes (fréquence décroissante, puis ordre alphabétique).afficher_rapport(texte): assemble le tout.
Pistes d'amélioration¶
- Ignorer une liste de mots vides (« dans », « pour », « avec »…) au lieu du filtre sur la longueur.
- Calculer un indice de lisibilité simple (mots par phrase, lettres par mot).
- Comparer deux textes : mots communs, mots propres à chacun (avec des ensembles).
- Détecter la langue (français ou anglais) d'après la fréquence de mots courants (« le », « the »).
- Lire le texte depuis un fichier et écrire le rapport dans un autre (chapitre 07).
Corrigés¶
Corrigé de l'exercice 1
email = " Jean.Dupont@Exemple.FR "
email = email.strip().lower()
identifiant, domaine = email.split("@")
prenom, nom = identifiant.split(".")
prenom = prenom.capitalize()
nom = nom.capitalize()
initiales = f"{prenom[0]}.{nom[0]}."
print(f"Email : {email}")
print(f"Identifiant : {identifiant}")
print(f"Domaine : {domaine}")
print(f"Nom complet : {prenom} {nom}")
print(f"Initiales : {initiales}")
Corrigé de l'exercice 2
Sortie : Lignes 1 et 4 : `if` **après** le `for` = filtre (moins d'éléments). Ligne 3 : `if/else` **avant** le `for` = transformation (autant d'éléments). `isupper()` reconnaît aussi les majuscules accentuées comme `É`. On aurait pu écrire la ligne 3 avec `max(n, 0)`.Corrigé de l'exercice 3
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
def cesar(texte, decalage):
"""Chiffre texte en décalant chaque lettre a-z de decalage positions."""
resultat = []
for caractere in texte:
if caractere in ALPHABET:
position = ALPHABET.index(caractere)
nouvelle_position = (position + decalage) % 26
resultat.append(ALPHABET[nouvelle_position])
else:
resultat.append(caractere)
return "".join(resultat)
def decoder(texte, decalage):
"""Déchiffre un texte chiffré par cesar avec le même décalage."""
return cesar(texte, -decalage)
secret = cesar("rendez-vous a midi !", 3)
print(secret)
print(decoder(secret, 3))
print(cesar("xyz", 2))
Corrigé de l'exercice 4
eleves = ["Alice", "Bob", "Chloé", "Dominique"]
notes = [15.5, 9, 12.25, 17]
print(f"{'#':<3}{'Élève':<11}{'Note':>6} Admis")
for rang, (eleve, note) in enumerate(zip(eleves, notes), start=1):
admis = "oui" if note >= 10 else "non"
print(f"{rang:<3}{eleve:<11}{note:>6.2f} {admis}")
moyenne = sum(notes) / len(notes)
print(f"Moyenne : {moyenne:.2f}")
Corrigé de l'exercice 5
produits = [
{"nom": "Clavier", "prix": 49.9, "stock": 12},
{"nom": "souris", "prix": 19.9, "stock": 0},
{"nom": "Écran", "prix": 189.0, "stock": 4},
{"nom": "casque", "prix": 49.9, "stock": 7},
]
# 1. Prix croissant, puis nom insensible à la casse
par_prix = sorted(produits, key=lambda p: (p["prix"], p["nom"].lower()))
print([p["nom"] for p in par_prix])
# 2. Le plus cher
print(max(produits, key=lambda p: p["prix"])["nom"])
# 3. Produits en stock, ordre alphabétique sans casse
en_stock = [p["nom"] for p in produits if p["stock"] > 0]
print(sorted(en_stock, key=str.lower))
# 4. Valeur du stock
valeur = sum(p["prix"] * p["stock"] for p in produits)
print(round(valeur, 2))
Corrigé de l'exercice 6
PETITS_MOTS = {"le", "la", "les", "de", "des", "du", "et", "en", "un", "une"}
def acronyme(expression):
"""Renvoie l'acronyme formé des initiales des mots qui ne sont pas des petits mots."""
return "".join(mot[0].upper() for mot in expression.split() if mot.lower() not in PETITS_MOTS)
def titre_propre(texte):
"""Met une majuscule aux mots importants et réduit les espaces."""
mots = texte.lower().split()
resultat = []
for i, mot in enumerate(mots):
if i == 0 or mot not in PETITS_MOTS:
resultat.append(mot.capitalize())
else:
resultat.append(mot)
return " ".join(resultat)
print(acronyme("société nationale des chemins de fer"))
print(acronyme("organisation des nations unies"))
print(titre_propre(" le seigneur des anneaux et la communauté de l'anneau "))
Corrigé de l'exercice 7
def signature(mot):
"""Renvoie les lettres du mot, en minuscules, triées alphabétiquement."""
return "".join(sorted(mot.lower()))
def sont_anagrammes(mot1, mot2):
"""Renvoie True si les deux mots sont des anagrammes."""
return signature(mot1) == signature(mot2)
def grouper_anagrammes(mots):
"""Renvoie les groupes d'au moins deux anagrammes, triés."""
groupes = {}
for mot in mots:
cle = signature(mot)
if cle not in groupes:
groupes[cle] = []
groupes[cle].append(mot)
resultat = [sorted(groupe, key=str.lower) for groupe in groupes.values() if len(groupe) >= 2]
return sorted(resultat, key=lambda groupe: groupe[0].lower())
print(signature("Niche"))
print(sont_anagrammes("chien", "Niche"))
print(sont_anagrammes("chat", "tache"))
mots = ["chien", "niche", "chine", "rage", "gare", "chat", "ironique", "onirique", "Agre"]
print(grouper_anagrammes(mots))
Corrigé de l'exercice 8
def normaliser_plaque(saisie):
"""Renvoie la plaque au format AB-123-CD, ou None si la saisie est invalide."""
brut = saisie.strip().upper().replace("-", "").replace(" ", "")
if len(brut) != 7:
return None
debut, chiffres, fin = brut[:2], brut[2:5], brut[5:]
lettres_ok = all(c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" for c in debut + fin)
if not lettres_ok or not chiffres.isdigit():
return None
return f"{debut}-{chiffres}-{fin}"
for saisie in ["AB-123-CD", "ab123cd", " xy 987 zt ", "A1-234-BC", "AB-12-CD", "ÉÉ-123-AA"]:
print(f"{saisie!r:15} → {normaliser_plaque(saisie)}")
Corrigé du mini-projet
ACCENTS = {
"à": "a", "â": "a", "ä": "a",
"é": "e", "è": "e", "ê": "e", "ë": "e",
"î": "i", "ï": "i",
"ô": "o", "ö": "o",
"ù": "u", "û": "u", "ü": "u",
"ç": "c", "œ": "oe", "æ": "ae",
}
PONCTUATION = ".,;:!?'\"()[]«»-–—…’"
def normaliser(texte):
"""Met le texte en minuscules et retire les accents."""
texte = texte.lower()
for accentue, simple in ACCENTS.items():
texte = texte.replace(accentue, simple)
return texte
def extraire_mots(texte):
"""Renvoie la liste des mots du texte, sans ponctuation."""
for signe in PONCTUATION:
texte = texte.replace(signe, " ")
return texte.split()
def compter_phrases(texte):
"""Compte les phrases : morceaux non vides entre deux signes . ! ou ?"""
for signe in "!?":
texte = texte.replace(signe, ".")
morceaux = texte.split(".")
return len([m for m in morceaux if m.strip() != ""])
def frequences(elements):
"""Renvoie un dictionnaire {element: nombre d'apparitions}."""
compteur = {}
for element in elements:
compteur[element] = compteur.get(element, 0) + 1
return compteur
def top(compteur, n):
"""Renvoie les n paires (element, nombre) les plus fréquentes."""
paires = sorted(compteur.items(), key=lambda p: (-p[1], p[0]))
return paires[:n]
def afficher_top(titre, paires):
"""Affiche un classement avec une barre proportionnelle."""
print(f"\n{titre}")
maximum = paires[0][1]
for rang, (element, nombre) in enumerate(paires, start=1):
barre = "#" * round(nombre / maximum * 20)
print(f" {rang}. {element:<12}{nombre:>3} {barre}")
def afficher_rapport(texte):
"""Analyse le texte et affiche un rapport complet."""
mots = extraire_mots(texte.lower())
mots_normalises = extraire_mots(normaliser(texte))
nb_caracteres = len(texte)
nb_sans_espaces = len([c for c in texte if not c.isspace()])
longueur_moyenne = sum(len(m) for m in mots) / len(mots)
plus_long = max(mots, key=len)
print("=" * 40)
print(f"{'RAPPORT D’ANALYSE':^40}")
print("=" * 40)
print(f"Caractères (total) : {nb_caracteres}")
print(f"Caractères (hors espaces): {nb_sans_espaces}")
print(f"Mots : {len(mots)}")
print(f"Mots différents : {len(set(mots))}")
print(f"Phrases : {compter_phrases(texte)}")
print(f"Longueur moyenne des mots: {longueur_moyenne:.2f}")
print(f"Mot le plus long : {plus_long} ({len(plus_long)} lettres)")
mots_longs = [m for m in mots_normalises if len(m) >= 4]
afficher_top("Mots les plus fréquents (4 lettres et +) :", top(frequences(mots_longs), 5))
lettres = [c for c in normaliser(texte) if c.isalpha()]
afficher_top("Lettres les plus fréquentes :", top(frequences(lettres), 5))
texte = """Python est un langage de programmation. Python est apprécié
pour sa lisibilité : un programme Python se lit presque comme de l'anglais !
Les débutants apprécient Python, les experts aussi. Pourquoi ? Parce que
le langage est simple, puissant et polyvalent. Programmer en Python,
c'est programmer avec plaisir."""
afficher_rapport(texte)
========================================
RAPPORT D’ANALYSE
========================================
Caractères (total) : 309
Caractères (hors espaces): 262
Mots : 47
Mots différents : 35
Phrases : 6
Longueur moyenne des mots: 5.32
Mot le plus long : programmation (13 lettres)
Mots les plus fréquents (4 lettres et +) :
1. python 5 ####################
2. langage 2 ########
3. programmer 2 ########
4. anglais 1 ####
5. apprecie 1 ####
Lettres les plus fréquentes :
1. e 33 ####################
2. p 22 #############
3. a 21 #############
4. s 19 ############
5. t 19 ############
Récapitulatif¶
- Une chaîne est une séquence immuable : indices, slicing,
in,lenetforfonctionnent, mais toute « modification » crée une nouvelle chaîne qu'il faut récupérer. - Slicing :
s[debut:fin:pas],finexclu ;s[::-1]inverse ; pas d'erreur en cas de dépassement. - Méthodes clés :
lower/upper/capitalize/title,strip,find/count/startswith/endswith,replace,isdigit/isalpha,split(chaîne → liste) etjoin(liste → chaîne, appelé sur le séparateur). - Les f-strings acceptent des formats :
{x:.2f},{nom:<10},{n:>5},{p:.1%},{x=}. enumerate(liste, start=1)donne(indice, valeur);zip(a, b)parcourt plusieurs listes en parallèle.sorted(..., key=fonction, reverse=True)trie selon un critère ; une clé tuple permet plusieurs critères.lambda params: expressioncrée une petite fonction anonyme, idéale pourkey.- Compréhensions :
[expr for x in seq if cond](liste),{k: v for ...}(dictionnaire),{expr for ...}(ensemble) ;iffinal = filtre,a if cond else ben tête = transformation. sum(...),any(...),all(...),max(...)acceptent une expression génératrice.
Checklist de validation¶
- Je sais extraire n'importe quel morceau d'une chaîne avec le slicing.
- Je sais nettoyer une saisie avec
strip()etlower()et en récupérer le résultat. - Je sais passer d'une chaîne à une liste avec
splitet revenir avecjoin. - Je sais aligner un tableau avec les f-strings.
- J'utilise
enumerateau lieu derange(len(...)). - Je sais trier une liste de dictionnaires avec
sortedet unelambda. - Je sais écrire une compréhension avec filtre, et une avec transformation
if/else. - J'ai terminé l'analyseur de texte.