Partant de la solution de la dernière question du TD précédent, on veut maintenant en plus forcer l'utilisateur à entrer des nombres, et pas juste stopper le programme avec un message dans le cas contraire. Pour cela, demander à l'utilisateur le 1er nombre indéfiniment tant qu'il / elle n'a pas entré un nombre correct, en précisant bien à chaque fois ce qui est attendu, et afficher à chaque fois l'erreur (texte entré n'est pas un nombre). Faire de même pour le second nombre.
On veut aussi que l'utilisateur puisse entrer des nombres à virgule, comme par exemple 2.5, 8.04, etc...
# Obtenir nombre 1.
# ↓ On part du fait que nb1 n'est pas un nombre, même si on ne le connait pas encore
nb1_est_pas_un_nombre = True
# ↓ Ainsi la boucle peut être exécutée au moins une fois
while nb1_est_pas_un_nombre:
# ↓ On récupère nb1 sous forme de str
# ↓ On reste en dehors du try car aucune erreur possible encore
nb1 = input("Entrez nombre 1: ")
try:
# ↓ On tente de convertir nb1 en float
# ↓ Cela peut provoquer une erreur, donc on doit être dans un bloc try
nb1 = float(nb1)
# ↓ si ça a fonctionné, on change la variable pour stopper la boucle
nb1_est_pas_un_nombre = False
except ValueError:
# En cas d'erreur dans le bloc try, ce bloc sera exécuté
print(nb1, "n'est pas un nombre")
# Obtenir nombre 2: on fait exactement la même chose que pour nb1.
nb2_est_pas_un_nombre = True
while nb2_est_pas_un_nombre:
nb2 = input("Entrez nombre 2: ")
try:
nb2 = float(nb2)
nb2_est_pas_un_nombre = False
except ValueError:
print(nb2, "n'est pas un nombre")
# Obtenir opérateur.
op = input("Operation? (+,-,*,/): ")
# Execution de l'opération.
# On a juste retiré le try-except par rapport à l'exercice précédent,
# car plus rien ici ne provoque d'erreur potentielle.
if op == "+":
print(nb1, op, nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, op, nb2, "=", nb1 - nb2)
elif op == "*":
print(nb1, op, nb2, "=", nb1 * nb2)
elif op == "/":
print(nb1, op, nb2, "=", nb1 / nb2)
else:
print("Opération inconnue: ", op)
Entrez nombre 1: 4.2
Entrez nombre 2: 5
Operation? (+,-,*,/): +
4.2 + 5.0 = 9.2
>
Entrez nombre 1: t
t n'est pas un nombre
Entrez nombre 1: y
y n'est pas un nombre
Entrez nombre 1: 4.2
Entrez nombre 2: 5
Operation? (+,-,*,/): +
4.2 + 5.0 = 9.2
>
Remarques:
float() au lieu de int(), pour que l'utilisateur puisse entrer des nombres à virgule.
nb1_est_pas_un_nombre décrit bien ce que la variable est censée avoir comme
valeur, et donc aide à comprendre son rôle dans le programme. Son type de donnée est également clair par son nom: c'est vrai ou c'est
faux, c'est donc un bool. Si on l'avait nommée nb_correct par exemple, on pourrait croire en lisant le code
que la variable contient le nombre correct lui-même, ce qui n'est pas le cas. Un nom trop court comme x ou a,
ne donnerait aucune information, rendrait le programme difficile à lire, et augmenterait les probabilité d'écrire du code incorrect.
On a écrit à la ligne 14 nb1_est_pas_un_nombre = False ce qui veut dire qu'on a donc bien nb1
sous forme de nombre (sous forme de float et non plus sous forme de str), hors comment peut-on être sûr de ça
à cette ligne?
Parce que dans un bloc try:, chaque ligne peut s'exécuter à condition que toutes les précédentes (du bloc
try:) ont bien pu être exécutées sans erreur. La première ligne du bloc qui provoquera une erreur stoppera
immédiatement le bloc try:, et c'est le bloc except: qui va démarrer.
Ainsi, à la ligne 14, la ligne 12 s'est forcément bien exécutée, ce qui veut dire que l'appel à la fonction float(nb1) a
réussi, et que donc nb1 a pu être converti de str vers float sans problème.
Pour tester si une variable ne contient pas un nombre, certains écrivent le test suivant: (qui est faux, et inutile, voir plus bas pourquoi)
nb = "10"
# ↓ La condition vaut True car "10" est différent du type int.
if nb != int:
print("Pas un nombre")
Le test est faux, car il compare la valeur de la variable avec le type int, hors il faudrait comparer
le type de la valeur de la variable. Vous savez obtenir le type d'une donnée avec la fonction pré-définie type().
Donc la version correcte serait:
nb = "10"
# ↓ La condition vaut encore True car le type de "10" est str,
# hors str != int,
# donc nb n'est toujours PAS considéré comme un nombre.
if type(nb) != int:
print("Pas un nombre")
De toute façon cette approche est inutile pour cet exercice, car on ne peut obtenir le nombre de l'utilisateur que sous forme de
str en premier lieu, grâce à la fonction input(). Puis il faut tenter de convertir en nombre avec un appel
à int() ou float(). Ces appels provoqueront une erreur si le texte de l'utilisateur ne représente pas un
nombre, le test type(nb) != int ne pourra donc jamais être utilisé car il y aura erreur avant. Seules les exceptions
avec des blocs try-except peuvent nous aider dans cette situation.
Une version alternative avec une seule boucle while pour les deux nombres, au lieu de chacun sa boucle:
nb1_est_pas_un_nombre = True
nb2_est_pas_un_nombre = True
while nb1_est_pas_un_nombre or nb2_est_pas_un_nombre:
if nb1_est_pas_un_nombre:
nb1 = input("Entrez nombre 1: ")
try:
nb1 = float(nb1)
nb1_est_pas_un_nombre = False
except ValueError:
print(nb1, "n'est pas un nombre")
if nb2_est_pas_un_nombre:
nb2 = input("Entrez nombre 2: ")
try:
nb2 = float(nb2)
nb2_est_pas_un_nombre = False
except ValueError:
print(nb2, "n'est pas un nombre")
# Obtenir opérateur
op = input("Operation? (+,-,*,/): ")
# Execution de l'opération
if op == "+":
print(nb1, op, nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, op, nb2, "=", nb1 - nb2)
elif op == "*":
print(nb1, op, nb2, "=", nb1 * nb2)
elif op == "/":
print(nb1, op, nb2, "=", nb1 / nb2)
else:
print("Opération inconnue: ", op)
Cette solution est un peu moins pratique pour l'utilisateur car si le premier nombre n'en est pas un, le message d'erreur s'affiche, mais le nombre 2 est demandé ensuite, au lieu de redemander nb1. De plus, cette solution s'adapte mal pour la question suivante, donc privilégier la première approche.
Dans cette question, on n'ajoute aucune nouvelle fonctionnalité à notre programme, on adapte simplement le code pour le simplifier et le raccourcir.
Partant de la solution de la question précédente, on veut simplifier le code en éliminant le code redondant (redondant signifie
dupliqué, identique, répété). On peut voir qu'on fait pratiquement la même chose pour obtenir chacun des deux nombres
nb1 et nb2, donc mettre dans une fonction le code pour obtenir un seul nombre.
Donner un nom explicite à la fonction, de façon à ce qu'on comprenne, rien que par son nom, ce qu'elle est censée faire et censée retourner comme résultat.
Puis appeler cette fonction 2 fois: une fois pour obtenir de l'utilisateur le 1er nombre, puis une seconde fois pour obtenir le 2ème nombre.
Le reste du programme reste inchangé.
# On définit notre fonction, en lui donnant un nom explicite.
# Aucun paramètre n'est nécessaire pour ce qu'on souhaite faire.
def obtenir_nb_de_utilisateur():
# Le code à l'intérieur est identique à la question précédente.
# On change juste nb1 ou nb2 en nb, car la fonction ne se
# préoccupe que de récupérer un seul nombre,
# et de retourner ce nombre: la fonction ne se préoccupe pas
# de à quoi servira ce nombre, elle fait juste son travail à elle.
nb_est_pas_un_nombre = True
while nb_est_pas_un_nombre:
nb = input("Entrez un nombre: ")
try:
nb = float(nb)
nb_est_pas_un_nombre = False
except ValueError:
print(nb, "n'est pas un nombre")
# ↓ On n'oublie surtout pas de retourner,
# au code qui va appeler cette fonction,
# le nombre de l'utilisateur.
return nb
# ↓ On peut maintenant appeller notre fonction 2 fois,
# pour obtenir nos deux nombres.
nb1 = obtenir_nb_de_utilisateur()
nb2 = obtenir_nb_de_utilisateur()
# ↓ Le reste est identique à la question précédente ↓
# Opérateur
op = input("Operation? (+,-,*,/): ")
# Execution de l'opération
if op == "+":
print(nb1, op, nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, op, nb2, "=", nb1 - nb2)
elif op == "*":
print(nb1, op, nb2, "=", nb1 * nb2)
elif op == "/":
print(nb1, op, nb2, "=", nb1 / nb2)
else:
print("Opération inconnue: ", op)
Entrez un nombre: 3.5
Entrez un nombre: 1.1
Operation? (+,-,*,/): +
3.5 + 1.1 = 4.6
>
Entrez un nombre: y
y n'est pas un nombre
Entrez un nombre: u
u n'est pas un nombre
Entrez un nombre: 4.5
Entrez un nombre: i
i n'est pas un nombre
Entrez un nombre: p
p n'est pas un nombre
Entrez un nombre: 6
Operation? (+,-,*,/): +
4.5 + 6.0 = 10.5
>
Remarques:
A l'affichage, l'utilisateur ne sait plus s'il entre le 1er ou le 2ème nombre, car le message est identique dans les deux cas. Pour éviter ça on pourrait rajouter un paramètre à notre fonction pour modifier le message si besoin, au moment de l'appel de la fonction. Par exemple:
# On rajoute un paramètre pour contrôler le message.
# Le nom du paramètre est important, il doit décrire son rôle,
# et donner une information sur le type attendu, ici c'est à dire str.
def obtenir_nb_de_utilisateur(message):
nb_est_pas_un_nombre = True
while nb_est_pas_un_nombre:
# ↓ On utilise le paramètre message ici:
# on le passe comme paramètre à la fonction input().
nb = input(message)
try:
nb = float(nb)
nb_est_pas_un_nombre = False
except ValueError:
print(nb, "n'est pas un nombre")
return nb
# ↓ On peut désormais passer un message différent à chaque appel.
nb1 = obtenir_nb_de_utilisateur("Nombre 1: ")
nb2 = obtenir_nb_de_utilisateur("Nombre 2: ")
op = input("Operation? (+,-,*,/): ")
if op == "+":
print(nb1, op, nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, op, nb2, "=", nb1 - nb2)
elif op == "*":
print(nb1, op, nb2, "=", nb1 * nb2)
elif op == "/":
print(nb1, op, nb2, "=", nb1 / nb2)
else:
print("Opération inconnue: ", op)
Nombre 1: 4
Nombre 2: 5
Operation? (+,-,*,/): +
4.0 + 5.0 = 9.0
>
Nombre 1: y
y n'est pas un nombre
Nombre 1: u
u n'est pas un nombre
Nombre 1: 4
Nombre 2: 5
Operation? (+,-,*,/): +
4.0 + 5.0 = 9.0
>
Partant de la solution de l'exercice précédent, forcer l'utilisateur à ne pouvoir entrer que l'une des 4 opérations autorisées: addition, soustraction, division, multiplication.
S'il / elle entre n'importe quoi d'autre, le programme affiche un message d'erreur adapté, et redemande encore et encore l'opération, jusqu'à ce que celle-ci soit l'une de celles permises.
def avoir_nombre_utilisateur(message):
nb_est_correct = False
while not nb_est_correct:
txt_utilisateur = input(message)
try:
nombre = float(txt_utilisateur)
nb_est_correct = True
except ValueError:
print(txt_utilisateur, "n'est pas un nombre")
return nombre
# Obtenir nombres
nb1 = avoir_nombre_utilisateur("Nombre 1: ")
nb2 = avoir_nombre_utilisateur("Nombre 2: ")
# Obtenir op
opération_est_correcte = False
while not opération_est_correcte:
op = input("Opération (+,-,*,/): ")
# ↓ L'opérateur booléen "in" renvoit True ou False.
# True si ce qui est à sa gauche EST DANS la séquence à sa droite,
# donc ici si op est égal à n'importe lequel des éléments de la liste.
opération_est_correcte = op in ["+", "-", "/", "*", ]
if not opération_est_correcte:
print(op, "n'est pas une opération supportée")
# Calculer et afficher résultat
if op == "+":
print(nb1, "+", nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, "-", nb2, "=", nb1 - nb2)
elif op == "/":
print(nb1, "/", nb2, "=", nb1 / nb2)
else:
print(nb1, "*", nb2, "=", nb1 * nb2)
Remarques:
Ne pas confondre le try-except avec le if-else.
if-else sert à faire telle ou telle action selon une condition, par exemple si l'utilisateur n'a pas entré une opération
supportée (+,-,/,*).
try-except sert à garder le contrôle sur l'exécution du programme en cas d'erreur (erreur au sens exécution impossible
par Python, pas une erreur du point de vue de l'utilisateur), par exemple si on tente de transformer en int ou float
le texte de l'utilisateur.
Comment savoir s'il faut utiliser try-except ou if-else?
Si un morceau de code que vous voulez exécuter peut provoquer des erreurs qui sont affichées en rouge dans la console, et qui
stoppent le programme, il faut un try-except pour intercepter l'erreur si elle survient, et vous permettre de continuer
l'exécution du programme.
Si le code ne provoque pas d'erreur potentiellement, mais que vous voulez faire des actions différentes selon une condition, alors
utiliser un if-else.
Une version alternative de op in ["+", "-", "/", "*", ] pourrait être:
opération_est_correcte = op == "+" or op == "-" or op == "/" or op == "*"
Partant de la solution de l'exercice précédent, répéter l'ensemble du programme indéfiniment, pour permettre à l'utilisateur de faire autant de calculs qu'il / elle souhaite.
A chaque fois qu'un calcul a été réalisé, demander à l'utilisateur s'il / elle souhaite continuer ou arrêter. Si l'utilisateur décide d'arrêter, le programme se termine. Sinon il recommence en demandant à nouveaux deux nombre, l'opération et affiche le résultat.
def avoir_nombre_utilisateur(message):
nb_est_correct = False
while not nb_est_correct:
txt_utilisateur = input(message)
try:
nombre = float(txt_utilisateur)
nb_est_correct = True
except:
print(txt_utilisateur, "n'est pas un nombre")
return nombre
# Boucle pour répéter les calculs
utilisateur_veut_continuer = True
while utilisateur_veut_continuer:
nb1 = avoir_nombre_utilisateur("Nombre 1: ")
nb2 = avoir_nombre_utilisateur("Nombre 2: ")
opération_est_correcte = False
while not opération_est_correcte:
op = input("Opération (+,-,*,/): ")
opération_est_correcte = op in ["+", "-", "/", "*", ]
if not opération_est_correcte:
print(op, "n'est pas une opération supportée")
if op == "+":
print(nb1, "+", nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, "-", nb2, "=", nb1 - nb2)
elif op == "/":
print(nb1, "/", nb2, "=", nb1 / nb2)
else:
print(nb1, "*", nb2, "=", nb1 * nb2)
# Demander utilisateur si veut continuer
réponse_utilisateur = input("Faire nouveau calcul? (o pour oui): ")
utilisateur_veut_continuer = réponse_utilisateur == "o"
Remarques:
Sur la dernière ligne, ne pas confondre = avec ==.
== est un opérateur booléen, qui vaut True quand les deux éléments à gauche et à droite sont égaux. Sur
cette ligne de code en particulier, == est exécuté en premier, et vaut True si
réponse_utilisateur est égal à "o", False sinon.
= est l'instruction d'affectation de valeur à une variable, et sera exécuté APRÈS le ==, c'est à dire la
variable utilisateur_veut_continuer prendra la valeur du résultat du test == (c'est à dire
True ou False).
Partant de la solution de l'exercice précédent, on veut rendre le code plus lisible et simple à comprendre. On n'ajoute aucune nouvelle fonctionnalité du point de vue de l'utilisateur, on adapte le code pour l'améliorer.
Mettre dans une fonction tout le code qui sert à un seul calcul, et donc PAS le code qui sert à répéter les calculs si l'utilisateur souhaite en faire d'autres. Utiliser / exécuter ensuite cette fonction avec le code qui répète les caluls.
def avoir_nombre_utilisateur(message):
nb_est_correct = False
while not nb_est_correct:
txt_utilisateur = input(message)
try:
nombre = float(txt_utilisateur)
nb_est_correct = True
except:
print(txt_utilisateur, "n'est pas un nombre")
return nombre
# Définition de notre seconde fonction
def faire_calcul_utilisateur():
nb1 = avoir_nombre_utilisateur("Nombre 1: ")
opération_est_correcte = False
while not opération_est_correcte:
op = input("Opération (+,-,*,/): ")
opération_est_correcte = op in ["+", "-", "/", "*", ]
if not opération_est_correcte:
print(op, "n'est pas une opération supportée")
nb2 = avoir_nombre_utilisateur("Nombre 2: ")
if op == "+":
print(nb1, "+", nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, "-", nb2, "=", nb1 - nb2)
elif op == "/":
print(nb1, "/", nb2, "=", nb1 / nb2)
else:
print(nb1, "*", nb2, "=", nb1 * nb2)
utilisateur_veut_continuer = True
while utilisateur_veut_continuer:
# Appel de la fonction
faire_calcul_utilisateur()
réponse_utilisateur = input("Faire nouveau calcul? (o pour oui): ")
utilisateur_veut_continuer = réponse_utilisateur == "o"
Remarques:
avoir_nombre_utilisateur dans notre seconde fonction faire_calcul_utilisateur. Il
est fréquent en programmation d'utiliser nos fonctions dans d'autres de nos fonctions.
La fonction avoir_nombre_utilisateur retourne un résultat avec l'instruction return. En revanche la
fonction faire_calcul_utilisateur ne retourne aucun résultat, car c'est ce qu'on souhaite: qu'elle fasse tout le travail de
demander les nombres et l'opérateur à l'utilisateur et lui affiche le résultat. On a décidé que la fonction ne sert pas à renvoyer un
quelconque résultat, seul nous importe qu'elle fasse ce qu'elle doit faire.
Dans notre boucle while tout en bas, on veut juste appeler la fonction, et qu'elle fasse elle-même tout le
calcul et l'affichage. Dans ce cas, un return dans la fonction est inutile.
Rappelez-vous cependant que même si on n'écrit aucun return, une fonction retourne quand même None par
défaut.
On ne va pas ajouter de nouvelle fonctionnalité à notre programme, mais on va utiliser une autre approche que les exceptions (qui sont l'approche idéale dans le contexte de cet exercice) pour savoir si un nombre entré par l'utilisateur est correct ou pas.
Copiez-collez la fonction qui vous sert à obtenir un nombre de l'utilisateur, afin d'avoir une copie et de garder l'originale intacte. Dans cette
copie, que vous renommerez avec un nom adapté, n'utilisez plus les exceptions. Testez "à la main" (par votre propre code) si le texte entré est un
nombre correct (à virgule ou entier, et avec signe négatif présent ou pas). Vous aurez besoin de connaître break et
continue pour vous faciliter la tâche, à lire dans le cours, partie sur les boucles avancées.
Attention cette modification ne doit pas changer le comportement de la fonction, c'est-à-dire si le texte entré n'est pas un nombre, il faut le redemander encore à l'utilisateur jusqu'à ce qu'un nombre correct soit entré.
Utilisez votre nouvelle fonction dans votre programme pour la tester.
def avoir_nombre_utilisateur_exceptions(message):
nb_est_correct = False
while not nb_est_correct:
txt_utilisateur = input(message)
try:
nombre = float(txt_utilisateur)
nb_est_correct = True
except:
print(txt_utilisateur, "n'est pas un nombre")
return nombre
def avoir_nombre_utilisateur_verif_manuelle(message):
txt_utilisateur_est_un_nombre = False
while not txt_utilisateur_est_un_nombre:
txt_utilisateur = input(message)
indice_départ = 0
signe = txt_utilisateur[indice_départ]
virgule_rencontrée = False
if signe == "-":
indice_départ += 1
txt_utilisateur_est_un_nombre = True
for i in range(indice_départ, len(txt_utilisateur)):
c = txt_utilisateur[i]
if c == ".":
if not virgule_rencontrée:
virgule_rencontrée = True
else:
print(txt_utilisateur, "n'est pas un nombre")
txt_utilisateur_est_un_nombre = False
break
elif c not in "0123456789":
print(txt_utilisateur, "n'est pas un nombre")
txt_utilisateur_est_un_nombre = False
break
return float(txt_utilisateur)
# Définition de notre seconde fonction
def faire_calcul_utilisateur():
nb1 = avoir_nombre_utilisateur_verif_manuelle("Nombre 1: ")
opération_est_correcte = False
while not opération_est_correcte:
op = input("Opération (+,-,*,/): ")
opération_est_correcte = op in ["+", "-", "/", "*", ]
if not opération_est_correcte:
print(op, "n'est pas une opération supportée")
nb2 = avoir_nombre_utilisateur_verif_manuelle("Nombre 2: ")
if op == "+":
print(nb1, "+", nb2, "=", nb1 + nb2)
elif op == "-":
print(nb1, "-", nb2, "=", nb1 - nb2)
elif op == "/":
print(nb1, "/", nb2, "=", nb1 / nb2)
else:
print(nb1, "*", nb2, "=", nb1 * nb2)
utilisateur_veut_continuer = True
while utilisateur_veut_continuer:
# Appel de la fonction
faire_calcul_utilisateur()
réponse_utilisateur = input("Faire nouveau calcul? (o pour oui): ")
utilisateur_veut_continuer = réponse_utilisateur == "o"