Algo. et prog. 2 en
Logo de Python
Arnaud COUTURIER - Python 3.10

✏️ TP 1 - Calculatrice niv.2 ✔️

Question 1

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:

  • On utilise la fonction float() au lieu de int(), pour que l'utilisateur puisse entrer des nombres à virgule.
  • Bien nommer ses variables est très important. 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.

Question 2

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
    >

Question 3

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 == "*"
    					

Question 4

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).

Question 5

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:

  • On exécute notre fonction 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.

Question 6

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"