Autodidacte Projets personnels
Python — Erreurs faites pendant le projet password-checker
Pas une liste pour culpabiliser, mais une carte des patterns d’erreur à reconnaître pour ne plus les refaire. Chaque erreur ici est typique du débutant et conceptuellement instructive — donc précieuse pour qui apprend Python en ce moment.
Le contexte : pendant mon premier projet Python sérieux (un vérificateur de force de mot de passe), j’ai accumulé une série d’erreurs très révélatrices de l’écart entre la façon dont un débutant pense et la façon dont Python fonctionne réellement.
1. Le piège du return manquant
Ce que j’ai fait
def has_length(password):
enough_length = False
if len(password) >= 8:
enough_length = True
# ← pas de return !
Résultat : has_length("longpassword") renvoyait None au lieu de True.
Pourquoi c’est un piège
Une fonction Python sans return explicite renvoie automatiquement None. Pas d’erreur visible, mais ta fonction “fonctionne” et ne fait rien d’utile.
Bon réflexe
- Toute fonction qui doit produire une valeur doit avoir un
return. - Pour les fonctions booléennes, le pattern propre :
def has_length(password):
return len(password) >= 8
- Si tu as un
if : return Truequelque part, vérifie qu’il y a aussi unreturn Falsequelque part pour tous les autres chemins.
Comment le détecter en avance
Teste tes fonctions immédiatement avec print(ma_fonction(...)). Si tu vois None, tu as oublié un return.
2. .upper() vs .isupper() — la méthode qui transforme vs celle qui teste
Ce que j’ai fait
if char.upper(): # ← FAUX
has_upper = True
Pourquoi c’est un bug grave
.upper()renvoie la version majuscule du caractère :"a".upper()→"A".isupper()teste si le caractère est en majuscule :"a".isupper()→False
Avec if char.upper():, tu testes en réalité if "A": qui est truthy (toute chaîne non vide est considérée vraie). La condition est donc toujours vraie, peu importe le caractère.
Le concept derrière : truthy / falsy
En Python, dans un if, n’importe quelle valeur est convertie en booléen :
| Valeur | Évaluée comme |
|---|---|
"", [], {}, (), set(), 0, None, False (toute collection vide) | Falsy (faux) |
| Tout le reste (texte non vide, nombre ≠ 0, collection non vide, etc.) | Truthy (vrai) |
Bon réflexe
- Les méthodes en
is...()(isupper,islower,isdigit,isalpha,isalnum) testent, renvoient un booléen. - Les méthodes comme
upper,lower,strip,replacetransforment, renvoient une nouvelle chaîne. - Le préfixe
isen anglais = “est-ce que c’est…” → test → booléen. C’est une convention de nommage qu’on retrouve dans énormément de langages :isEmpty,isValid,isAdmin…
3. elif au lieu de if indépendants
Ce que j’ai fait
score = 0
if has_length(password):
score += 1
elif has_number(password): # ← FAUX (devrait être if, pas elif)
score += 1
elif has_uppercase(password):
score += 1
# ...
Pourquoi c’est un bug
elif = “sinon si”. Python ne teste les elif que si tous les if/elif précédents sont faux. Dès qu’un bloc passe, les autres sont sautés. Score maximum : 1.
Le bon pattern : des if indépendants
Pour cumuler des conditions, chaque test doit être un if séparé :
score = 0
if has_length(password):
score += 1
if has_number(password): # ← if "normal"
score += 1
if has_uppercase(password):
score += 1
Mémo
| Tu veux… | Tu utilises |
|---|---|
| Une alternative exclusive (un seul chemin pris) | if / elif / else |
| Plusieurs conditions indépendantes qui s’additionnent | ifs séparés |
| Tester si au moins une condition est vraie | if A or B or C: (renvoie UN seul résultat, ne cumule pas) |
4. Confusion entre or et “cumul”
Ce que j’ai fait
if has_length(password) or has_number(password) or has_uppercase(password):
score += 1
Pensé : “ça va ajouter 1 pour chaque condition vraie”.
Réalité : or produit un seul résultat (“est-ce qu’au moins une est vraie ?”). Le score += 1 ne s’exécute donc qu’une seule fois maximum.
Bon réflexe
oretandproduisent une seule valeur, point. Ici c’est un booléen, puisque mes fonctions renvoientTrueouFalse.- Pour cumuler, il faut une boucle ou une succession d’
ifs.
Précision pour plus tard : en Python, or renvoie en réalité l’un de ses deux opérandes, pas forcément un booléen. "" or "abc" vaut "abc", et "a" and "b" vaut "b".
5. Le copier-coller piégeux
Ce que j’ai fait
def has_lowercase(password):
for char in password:
if char.lower():
has_upper = True # ← oublié de renommer
return has_upper # ← oublié de renommer
J’avais copié has_uppercase et oublié de renommer les occurrences de has_upper en has_lower. Au passage, la copie a aussi recopié les erreurs des parties 1 et 2 : char.lower() au lieu de char.islower(), et aucun return False après la boucle.
Et plus tard :
checks = [has_length, has_number, has_uppercase, has_uppercase, has_specialcharacters]
# ↑↑↑
# copié 2 fois, has_lowercase oublié
Pourquoi c’est dangereux
Python n’a aucun moyen de détecter ce genre d’erreur. Le code tourne, mais ment : pas d’exception levée, pas d’avertissement, juste un résultat faux.
Bons réflexes
- Quand tu copies une fonction, renomme TOUT ligne par ligne avant d’écrire la logique.
- Quand tu construis une liste à partir d’éléments quasi-identiques, relis à voix haute en pointant chaque élément. Ridicule mais redoutablement efficace.
- Find & Replace de l’éditeur uniquement sur la sélection, pas tout le fichier.
6. print dans une fonction qui devrait return
Ce que j’ai fait
def evaluate_password(password):
# ... calcul du score ...
if score >= 4:
print(f"Strong, {score}/5") # ← imprime
# pas de return → renvoie None
print(evaluate_password(password)) # → imprime "Strong, X/5" puis "None"
Le principe : séparation des responsabilités
Par défaut, une fonction devrait faire UNE chose : calculer et renvoyer un résultat. C’est au code appelant de décider quoi faire de ce résultat (afficher, sauver, envoyer, ignorer).
def evaluate_password(password):
# ... calcul ...
if score >= 4:
return f"Strong, {score}/5" # ← renvoie, n'imprime pas
print(evaluate_password(password)) # le caller décide d'imprimer
Pourquoi c’est mieux
- Réutilisable : tu peux envoyer le résultat par mail, l’écrire dans un fichier, l’afficher dans une UI — sans modifier la fonction.
- Testable : tu peux tester
evaluate_password("abc") == "Weak, 1/5", ce qui est impossible si la fonctionprint. - Lisible : la séparation “calcul / affichage” est explicite.
L’exception
Une fonction conçue spécifiquement pour afficher (ex. print_report(), display_results()) — son nom dit clairement son intention. Là, le print à l’intérieur est légitime et la fonction ne renvoie souvent rien volontairement.
7. La f-string n’est PAS liée à print
Ce que je pensais
“Une f-string ne peut s’utiliser que dans un print.”
Réalité
Une f-string est juste une chaîne de caractères avec interpolation. C’est une valeur, pas une action. Tu peux faire tout ce que tu fais avec une chaîne normale :
name = "Alice"
msg = f"Hello, {name}" # construction
print(msg) # afficher
return msg # renvoyer (dans une fonction)
len(msg) # mesurer (12)
msg.upper() # transformer
fichier.write(msg) # écrire dans un fichier
Le concept derrière : valeur vs action
Une valeur (string, nombre, liste, bool…) existe en soi. Ce que tu fais avec est une action séparée (print, return, =, write, etc.). Construire une valeur et l’utiliser sont deux étapes distinctes — c’est une distinction qu’on retrouve dans tous les langages.
8. while quand il fallait for
Ce que j’avais essayé
while has_length(password) or has_number(password) or ... :
score += 1
break
J’espérais cumuler des points.
Pourquoi while ne convenait pas
while CONDITION:répète la même action tant que la condition est vraie.- La condition doit évoluer entre les itérations (sinon : boucle infinie).
- Mon besoin était de faire 5 actions différentes (tester 5 critères). Pas la même chose répétée.
Le bon outil : for sur une collection
checks = [has_length, has_number, has_uppercase, has_lowercase, has_specialcharacters]
for check in checks:
if check(password):
score += 1
Mémo : quel outil pour quel besoin ?
| Besoin | Outil |
|---|---|
| Répéter une action tant que une condition est vraie | while |
| Faire quelque chose pour chaque élément d’une collection | for |
| Faire un test booléen à un instant donné | if |
9. Les fonctions sont des objets (first-class functions)
Ce que j’ai découvert
checks = [has_length, has_number, has_uppercase, has_lowercase, has_specialcharacters]
# ↑ sans parenthèses !
Une fonction en Python est une valeur au même titre qu’un nombre ou une chaîne. Tu peux la stocker dans une variable, la mettre dans une liste, la passer en argument.
La différence cruciale
| Syntaxe | Signification |
|---|---|
has_length | La fonction elle-même (objet) |
has_length(password) | Appelle la fonction et donne son résultat |
C’est la différence entre une recette (instructions, posée dans le livre) et cuisiner le plat (exécution de la recette).
Pourquoi c’est puissant
Tu peux écrire :
for check in checks:
if check(password): # ← on appelle ici, avec ()
score += 1
Au lieu de 5 if répétés. C’est le pattern DRY (Don’t Repeat Yourself) en action — un des principes fondamentaux de la programmation propre.
10. Bonus : ce que j’ai bien fait (à reproduire)
Pas que des erreurs ! Ces réflexes étaient bons :
- Tester immédiatement chaque fonction avec
print(...)au lieu d’écrire 200 lignes avant de tester. - Une fonction = une responsabilité (5 fonctions séparées pour 5 critères, pas une mégafonction).
- Demander de l’aide quand bloqué plutôt que perdre des heures.
- Poser des questions sur le pourquoi (
whileau lieu d’if, f-string seulement dansprint, etc.) — c’est comme ça qu’on apprend en profondeur. - Lire avant d’agir quand on me proposait deux options (A : prudent / B : pousser plus loin) — choix conscient à chaque fois.
Conseil de relecture : revisite cette fiche dans 3 jours, puis 1 semaine, puis 1 mois. Si à chaque relecture tu te dis “ah oui c’est évident”, c’est que c’est rentré. Sinon, ré-applique le pattern dans un nouveau projet.