TD 0 : Types de données, saisie, affichage et lecture de code

Ce TD est le point de depart de la serie Python. On y manipule les quatre types que l'on croise tous les jours (int, float, str, bool), on apprend a faire entrer une donnee dans un programme et a la ressortir proprement, et on s'entraine a lire un programme pour dire ce qu'il affiche avant de le lancer.

Cette derniere competence est celle qui fait gagner le plus de temps par la suite : un etudiant qui sait derouler un code dans sa tete debogue ses propres programmes au lieu de les subir. Chaque exercice est suivi d'un rappel court sur la notion qu'il met en jeu, puis d'un corrige a deplier.

Testez les reponses dans un interpreteur Python avant de regarder le corrige : l'ordinateur est le seul correcteur qui ne se trompe jamais.

Python pandacodeur 1
20 exercices corrigés

Partie 1Les types de données

Sept exercices pour savoir, a tout instant, quel type contient une variable et ce qui arrive quand on les melange. La plupart des erreurs de debutant viennent de la.

Quel type se cache derrière la valeur ?

Pour chacune des expressions suivantes, dites ce que renvoie type(...) :

7
7.0
"7"
True
7 == 7.0

Puis verifiez dans l'interpreteur.

Rappel

Python associe un type a toute valeur, et type() vous le dit. Les quatre types de base : int (entier), float (nombre a virgule), str (chaine de caracteres), bool (vrai ou faux).

Le point qui trompe tout le monde : 7 et 7.0 ne sont pas du meme type, alors qu'ils representent la meme quantite.

Voir le corrigéMasquer le corrigé

Corrigé

type(7)         # <class 'int'>
type(7.0)       # <class 'float'>
type("7")       # <class 'str'>
type(True)      # <class 'bool'>
type(7 == 7.0)  # <class 'bool'>

Les trois premieres lignes montrent que le meme nombre 7 s'ecrit de trois facons differentes selon ce qu'on veut en faire : calculer avec (int), calculer avec des decimales (float), ou l'afficher comme du texte (str).

La derniere ligne merite une seconde de reflexion. 7 == 7.0 est une comparaison, pas une valeur : Python l'evalue d'abord, obtient True, et c'est le type de True qui est affiche. La comparaison est vraie parce que Python compare les valeurs numeriques, pas les types.

La division réserve une surprise

Donnez le resultat et le type de chacune de ces operations :

10 / 2
10 // 2
10 % 3
2 ** 10
7 / 7

Rappel

Python distingue deux divisions :

  • / est la division ordinaire. Elle renvoie toujours un float, meme quand le resultat tombe juste.
  • // est la division entiere : elle garde le quotient et jette la partie decimale.

% donne le reste, et ** est la puissance (pas ^, qui fait tout autre chose en Python).

Voir le corrigéMasquer le corrigé

Corrigé

10 / 2    # 5.0      float
10 // 2   # 5        int
10 % 3    # 1        int
2 ** 10   # 1024     int
7 / 7     # 1.0      float

Retenez la premiere et la derniere ligne : meme quand la division tombe rond, / produit un flottant. C'est voulu, pour que le type du resultat ne depende pas des valeurs : 10 / 2 et 10 / 3 renvoient tous deux un float.

10 % 3 vaut 1 car 10 = 3 × 3 + 1. Le couple // et % va souvent ensemble : l'un donne le quotient, l'autre le reste.

Passer d'un type à l'autre

Que valent ces conversions ?

int("42")
int(3.9)
int(-3.9)
float("3.14")
str(7) + str(3)

Attention a l'avant-derniere et a la derniere.

Rappel

Les fonctions int(), float() et str() fabriquent une nouvelle valeur dans le type demande.

Le piege classique : int() applique a un flottant tronque, il n'arrondit pas. Pour arrondir, c'est round().

Voir le corrigéMasquer le corrigé

Corrigé

int("42")        # 42
int(3.9)         # 3      et non 4
int(-3.9)        # -3     et non -4
float("3.14")    # 3.14
str(7) + str(3)  # '73'   et non 10

int(3.9) vaut 3 : la partie decimale est coupee, purement et simplement. Pour -3.9, la troncature ramene vers zero, donc -3. Si vous vouliez arrondir, il fallait round(3.9), qui donne 4.

La derniere ligne illustre le fait que + ne fait pas la meme chose selon les types : entre deux nombres il additionne, entre deux chaines il colle. "7" + "3" donne "73".

Pourquoi ce programme refuse de fonctionner

Un etudiant ecrit ceci pour calculer l'age de quelqu'un l'annee prochaine :

age = input("Quel est ton age ? ")
print("L'an prochain tu auras", age + 1)

Le programme s'arrete avec une erreur. Laquelle, pourquoi, et comment la corriger ?

Rappel

input() renvoie toujours une chaine de caracteres, meme quand l'utilisateur tape des chiffres. Si vous comptez calculer avec, il faut convertir : int(input(...)) ou float(input(...)).

C'est, de loin, l'erreur la plus frequente des premieres semaines.

Voir le corrigéMasquer le corrigé

Corrigé

L'erreur est :

TypeError: can only concatenate str (not "int") to str

Traduction : Python a recu "20" + 1. A gauche une chaine, a droite un entier. Il ne sait pas quoi en faire, et refuse de deviner.

La correction tient dans la conversion :

age = int(input("Quel est ton age ? "))
print("L'an prochain tu auras", age + 1)

On peut aussi convertir au moment du calcul, int(age) + 1, mais convertir tout de suite a la saisie est plus propre : ensuite age est un nombre partout dans le programme, et on n'y pense plus.

Les booléens sont des entiers déguisés

Que valent ces expressions ?

True + True
False * 7
bool(0)
bool("")
bool("False")
bool(-3)

Les deux dernieres sont les plus interessantes.

Rappel

En Python, bool est un cas particulier de int : True vaut 1 et False vaut 0 dans tout calcul.

Dans l'autre sens, bool(x) repond a la question « x est-il considere comme vrai ? ». Sont faux : 0, 0.0, la chaine vide, la liste vide et None. Tout le reste est vrai, y compris les nombres negatifs.

Voir le corrigéMasquer le corrigé

Corrigé

True + True    # 2
False * 7      # 0
bool(0)        # False
bool("")       # False
bool("False")  # True   !
bool(-3)       # True   !

bool("False") vaut True, et c'est logique une fois qu'on a compris la regle : "False" est une chaine de cinq caracteres, donc une chaine non vide, donc vraie. Le contenu du texte n'est jamais interprete. "0" est vrai lui aussi, pour la meme raison.

bool(-3) vaut True parce que la regle porte sur « different de zero », pas sur « positif ».

Quand l'ordinateur compte mal

Predisez l'affichage :

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(round(2.675, 2))
print(round(2.5))
print(round(3.5))

Au moins trois de ces lignes ne donnent pas ce qu'on attend spontanement.

Rappel

Un float est stocke en binaire sur un nombre fini de bits. Des nombres simples en base 10, comme 0,1, n'ont pas d'ecriture binaire exacte : ils sont memorises approches. Les minuscules erreurs s'accumulent.

Consequence pratique : ne comparez jamais deux flottants avec ==. Comparez plutot l'ecart a une tolerance.

Voir le corrigéMasquer le corrigé

Corrigé

print(0.1 + 0.2)          # 0.30000000000000004
print(0.1 + 0.2 == 0.3)   # False
print(round(2.675, 2))    # 2.67   et non 2.68
print(round(2.5))         # 2      et non 3
print(round(3.5))         # 4

Les deux premieres lignes sont la meme histoire : 0,1 et 0,2 sont stockes approches, leur somme tombe juste a cote de 0,3.

round(2.675, 2) donne 2,67 parce que la valeur reellement stockee est 2,67499999... : Python arrondit correctement un nombre qui n'est deja plus tout a fait 2,675.

Les deux dernieres lignes relevent d'une autre regle. Sur un cas parfaitement a mi-chemin, Python arrondit vers le pair le plus proche : 2,5 donne 2, et 3,5 donne 4. Ce n'est pas un bug, c'est la norme IEEE 754, choisie pour que les erreurs ne s'accumulent pas toujours dans le meme sens sur de longues series.

Pour comparer deux flottants :

abs((0.1 + 0.2) - 0.3) < 1e-9   # True

Découper une chaîne

Soit ville = "Douala". Que valent ?

len(ville)
ville[0]
ville[-1]
ville[2:5]
ville[:3]
ville[3:]

Puis dites ce que renvoie len("Yaoundé").

Rappel

Les caracteres d'une chaine sont numerotes a partir de 0. Un indice negatif compte depuis la fin : -1 est le dernier.

La tranche s[a:b] va de l'indice a inclus a l'indice b exclu. Omettre a signifie « depuis le debut », omettre b « jusqu'a la fin ».

Voir le corrigéMasquer le corrigé

Corrigé

Placons les indices :

  D   o   u   a   l   a
  0   1   2   3   4   5
 -6  -5  -4  -3  -2  -1
len(ville)    # 6
ville[0]      # 'D'
ville[-1]     # 'a'
ville[2:5]    # 'ual'   indices 2, 3, 4
ville[:3]     # 'Dou'
ville[3:]     # 'ala'

ville[2:5] renvoie trois caracteres et non quatre : la borne de droite est exclue. C'est deroutant au debut, mais cela rend une propriete tres pratique : s[:3] + s[3:] reconstitue toujours s, sans chevauchement ni trou.

Enfin, len("Yaoundé") depend de l'ecriture : si le « e » accentue est un seul caractere, la reponse est 7. Python compte les caracteres, pas les octets : un accent compte pour un.

Partie 2Lire et afficher

Faire entrer une valeur dans le programme, la mettre en forme, la ressortir lisible. input et print ont quelques subtilites qui valent le detour.

Saluer l'utilisateur

Ecrivez un programme qui demande son nom a l'utilisateur, puis affiche :

Bonjour Awa, bienvenue sur PandaCodeur !

Le nom affiche doit evidemment etre celui qui a ete saisi.

Rappel

Une f-string est une chaine prefixee par f dans laquelle on glisse des expressions entre accolades. C'est la facon la plus lisible de composer un texte avec des variables :

f"Bonjour {nom} !"
Voir le corrigéMasquer le corrigé

Corrigé

nom = input("Comment t'appelles-tu ? ")
print(f"Bonjour {nom}, bienvenue sur PandaCodeur !")

Trois autres ecritures donnent le meme resultat :

print("Bonjour " + nom + ", bienvenue sur PandaCodeur !")
print("Bonjour", nom, ", bienvenue sur PandaCodeur !")   # espace parasite
print("Bonjour {}, bienvenue !".format(nom))

La deuxieme est a eviter : print insere un espace entre ses arguments, on obtiendrait Awa , avec un espace avant la virgule. La f-string n'a pas ce defaut et reste la plus lisible.

Additionner deux nombres saisis

Demandez deux nombres entiers a l'utilisateur et affichez leur somme sous la forme :

12 + 7 = 19

Rappel

Chaque appel a input() lit une ligne. Pour obtenir un nombre utilisable, on enveloppe la saisie dans int() :

n = int(input("Un nombre : "))

Si l'utilisateur tape autre chose qu'un entier, le programme s'arrete sur une ValueError. C'est normal a ce stade.

Voir le corrigéMasquer le corrigé

Corrigé

a = int(input("Premier nombre : "))
b = int(input("Deuxieme nombre : "))
print(f"{a} + {b} = {a + b}")

Remarquez qu'une f-string accepte un calcul entre accolades, pas seulement un nom de variable. {a + b} est evalue au moment de l'affichage.

Si vous aviez ecrit print(f"{a} + {b} = {a} + {b}"), vous auriez obtenu 12 + 7 = 12 + 7 : les accolades affichent la valeur, elles ne calculent que ce qu'on leur donne a calculer.

Les réglages cachés de print

Qu'affiche exactement ce programme ? Indiquez les espaces et les retours a la ligne.

print("a", "b", "c")
print("a", "b", "c", sep="-")
print("a", "b", "c", sep="")
print("debut", end=" >>> ")
print("fin")

Rappel

print possede deux reglages discrets :

  • sep : ce qui est insere entre les valeurs. Par defaut, une espace.
  • end : ce qui est ajoute apres la derniere valeur. Par defaut, un retour a la ligne "\n".
Voir le corrigéMasquer le corrigé

Corrigé

a b c
a-b-c
abc
debut >>> fin

Les trois premieres lignes montrent sep a l'oeuvre : l'espace par defaut, puis un tiret, puis rien du tout.

Les deux dernieres instructions ne produisent qu'une seule ligne. En remplacant end par " >>> ", le premier print n'envoie plus de retour a la ligne : l'affichage suivant continue sur la meme ligne.

C'est le reglage a connaitre quand on veut construire un affichage en plusieurs morceaux.

Afficher un prix lisible

Le montant 1234567.891 doit s'afficher exactement ainsi :

1 234 567,89 FCFA

Separateur de milliers : une espace. Separateur decimal : une virgule. Deux decimales.

Rappel

Dans une f-string, on precise le format apres un deux-points :

  • {x:.2f} : deux chiffres apres la virgule ;
  • {x:,.2f} : en plus, une virgule tous les milliers.

Python formate a l'anglaise. Pour la convention francaise, on retouche ensuite la chaine avec replace().

Voir le corrigéMasquer le corrigé

Corrigé

montant = 1234567.891
texte = f"{montant:,.2f}"            # '1,234,567.89'
texte = texte.replace(",", " ")      # '1 234 567.89'
texte = texte.replace(".", ",")      # '1 234 567,89'
print(texte, "FCFA")

L'ordre des deux replace est essentiel. En commencant par le point, on obtiendrait 1,234,567,89, et la conversion suivante transformerait aussi les separateurs de milliers : le montant deviendrait illisible.

En remplacant d'abord les virgules, il ne reste plus qu'un seul point dans la chaine, celui des decimales. On peut alors le convertir sans risque.

En une seule expression, avec un caractere intermediaire :

print(f"{montant:,.2f}".replace(",", " ").replace(".", ",") + " FCFA")

Deux valeurs sur une seule ligne

L'utilisateur doit saisir deux entiers sur la meme ligne, separes par une espace :

Entrez deux nombres : 12 7
Somme : 19
Produit : 84

Ecrivez le programme.

Rappel

split() decoupe une chaine en une liste de morceaux. Sans argument, il coupe sur les espaces et ignore les espaces multiples :

"12   7".split()   # ['12', '7']

Les morceaux restent des chaines : il faut encore les convertir.

Voir le corrigéMasquer le corrigé

Corrigé

morceaux = input("Entrez deux nombres : ").split()
a = int(morceaux[0])
b = int(morceaux[1])
print("Somme :", a + b)
print("Produit :", a * b)

Version plus courte, avec un deballage direct :

a, b = input("Entrez deux nombres : ").split()
a, b = int(a), int(b)
print("Somme :", a + b)
print("Produit :", a * b)

Ou encore, en une ligne grace a map, qui applique int a chaque morceau :

a, b = map(int, input("Entrez deux nombres : ").split())

Les trois versions sont correctes. La premiere est la plus explicite, la derniere la plus courante chez les habitues.

Guillemets, tabulations et antislashs

Ecrivez les instructions qui produisent exactement cet affichage :

Il a dit : "bonjour"
Nom	Ville
Awa	Douala
C:\Users\Awa

La deuxieme et la troisieme ligne sont separees par des tabulations.

Rappel

Certains caracteres s'ecrivent avec un antislash : \n retour a la ligne, \t tabulation, \\ un antislash litteral, \" un guillemet.

Deux echappatoires utiles : utiliser des apostrophes autour d'une chaine qui contient des guillemets, et prefixer par r pour que les antislashs soient pris au pied de la lettre.

Voir le corrigéMasquer le corrigé

Corrigé

print('Il a dit : "bonjour"')
print("Nom\tVille")
print("Awa\tDouala")
print(r"C:\Users\Awa")

Premiere ligne : en entourant d'apostrophes, les guillemets interieurs ne posent aucun probleme. On aurait aussi pu ecrire "Il a dit : \"bonjour\"", moins lisible.

Derniere ligne : le prefixe r (pour raw) desactive l'interpretation des antislashs. Sans lui, il aurait fallu les doubler : "C:\\Users\\Awa". Et attention, "C:\Users" sans precaution ne produit pas ce qu'on croit : \U a une signification particuliere et declenche une erreur.

C'est la raison pour laquelle les chemins Windows s'ecrivent presque toujours avec un r devant.

Un reçu aligné

A partir de ces trois variables :

article = "Cahier"
quantite = 3
prix = 1500

produisez cet affichage, les nombres cales a droite sur dix colonnes :

Article   : Cahier
Quantite  :          3
Prix unit.:       1500
Total     :       4500

Rappel

Apres le deux-points d'une f-string, on peut demander un alignement sur une largeur donnee :

  • {x:>10} cale a droite sur 10 colonnes ;
  • {x:<10} cale a gauche ;
  • {x:^10} centre.

Par defaut, les nombres se calent a droite et le texte a gauche.

Voir le corrigéMasquer le corrigé

Corrigé

article = "Cahier"
quantite = 3
prix = 1500

print(f"Article   : {article}")
print(f"Quantite  : {quantite:>10}")
print(f"Prix unit.: {prix:>10}")
print(f"Total     : {quantite * prix:>10}")

Chaque nombre occupe dix colonnes, complete par des espaces a gauche : les chiffres des unites tombent donc tous les uns sous les autres, ce qui rend la colonne lisible.

Le total est calcule directement dans l'accolade. On aurait pu le ranger dans une variable avant, les deux se valent ici.

Pour aller plus loin, {prix:>10,} ajouterait en plus les separateurs de milliers.

Partie 3Que produit ce programme ?

On inverse l'exercice : le code est donne, c'est le resultat qu'il faut trouver. Prenez un papier, deroulez ligne a ligne, notez la valeur de chaque variable. Ne lancez le programme qu'apres avoir ecrit votre reponse.

Deux variables, une seule valeur copiée

Qu'affiche ce programme ?

a = 5
b = a
a = 9
print(a, b)

Rappel

b = a copie la valeur au moment de l'affectation. Les deux variables sont ensuite independantes : modifier l'une n'a aucun effet sur l'autre.

Une affectation ne cree jamais de lien durable entre deux noms.

Voir le corrigéMasquer le corrigé

Corrigé

9 5

Deroulons :

a = 5     # a vaut 5
b = a     # b recoit la valeur actuelle de a, c'est-a-dire 5
a = 9     # a vaut maintenant 9 ; b n'a pas bouge
print(a, b)   # 9 5

L'erreur serait de lire b = a comme « b suit a ». Non : c'est une photographie, prise une fois, a cet instant precis. La ligne suivante change a sans toucher a la photo.

Gardez ce resultat en tete, l'exercice 18 raconte une histoire tres differente avec une liste.

Échanger sans variable intermédiaire

Qu'affiche ce programme ?

x, y = 3, 8
x, y = y, x
print(x, y)

Expliquez pourquoi aucune variable temporaire n'est necessaire.

Rappel

Python evalue d'abord tout le membre de droite, puis distribue les valeurs a gauche. Dans x, y = y, x, le couple (8, 3) est construit avant qu'aucune affectation n'ait lieu.

Voir le corrigéMasquer le corrigé

Corrigé

8 3

Le detail de la deuxieme ligne :

x, y = y, x
#      ^^^^  evalue en premier : (8, 3) est mis de cote
# puis distribue : x recoit 8, y recoit 3

Dans beaucoup de langages, cet echange exige une variable tampon :

t = x
x = y
y = t

Python s'en passe parce que le membre de droite est integralement evalue avant la moindre affectation. Les anciennes valeurs sont deja en securite dans un couple temporaire quand x est ecrase.

Corrigé vidéo

La vidéo explique la méthode de résolution du thème indiqué. Adaptez-la aux données et aux consignes de cet exercice.

Algorithme 2 : Algorithme de permutation

Voir sur YouTube

Une cascade d'opérateurs augmentés

Quelle valeur est affichee ?

n = 10
n += 5
n *= 2
n -= 3
n //= 4
print(n)

Notez la valeur de n apres chaque ligne.

Rappel

n += 5 est l'ecriture abregee de n = n + 5. Le meme principe vaut pour -=, *=, /=, //=, %= et **=.

Chaque ligne part de la valeur courante de la variable : l'ordre des instructions change donc le resultat.

Voir le corrigéMasquer le corrigé

Corrigé

6

Ligne par ligne :

n = 10          # n = 10
n += 5          # n = 10 + 5  = 15
n *= 2          # n = 15 * 2  = 30
n -= 3          # n = 30 - 3  = 27
n //= 4         # n = 27 // 4 = 6     (et non 6.75)
print(n)        # 6

La derniere ligne est le piege : //= fait une division entiere. 27 divise par 4 donne 6 reste 3, on garde 6.

Avec /= a la place, n vaudrait 6.75 et changerait de type au passage, devenant un float.

Deux noms pour une seule liste

Qu'affiche ce programme ? Comparez avec l'exercice 15.

a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)

Rappel

Une liste est un objet modifiable. b = a ne recopie pas la liste : les deux noms designent desormais le meme objet en memoire.

Modifier l'objet par l'un des noms le modifie pour les deux. Pour obtenir une vraie copie, il faut la demander : a[:], list(a) ou a.copy().

Voir le corrigéMasquer le corrigé

Corrigé

[1, 2, 3, 4]
[1, 2, 3, 4]

Les deux affichages sont identiques, et beaucoup de debutants ne s'y attendent pas apres avoir vu l'exercice 15.

La difference tient au type. Dans l'exercice 15, a = 9 reaffectait le nom a a une autre valeur, sans toucher a l'ancienne. Ici, b.append(4) ne reaffecte rien : il modifie l'objet lui-meme, et cet objet est celui que a designe aussi.

a = [1, 2, 3]
b = a[:]          # copie reelle
b.append(4)
print(a)          # [1, 2, 3]
print(b)          # [1, 2, 3, 4]

On peut verifier le partage avec id(), qui donne l'adresse de l'objet : dans le premier cas id(a) == id(b) est vrai, dans le second il est faux.

Qui passe en premier ?

Calculez a la main, puis verifiez :

print(2 + 3 * 4 ** 2 // 5 - 1)

Detaillez l'ordre dans lequel Python effectue les operations.

Rappel

Par ordre de priorite decroissante : **, puis * / // %, puis + -.

A priorite egale, on evalue de gauche a droite : sauf **, qui s'evalue de droite a gauche. Dans le doute, mettez des parentheses : elles ne coutent rien et rendent le code lisible.

Voir le corrigéMasquer le corrigé

Corrigé

10

Le deroulement :

2 + 3 * 4 ** 2 // 5 - 1

4 ** 2   -> 16        la puissance d'abord
2 + 3 * 16 // 5 - 1

3 * 16   -> 48        puis * et //, de gauche a droite
2 + 48 // 5 - 1

48 // 5  -> 9
2 + 9 - 1

2 + 9    -> 11        enfin + et -
11 - 1   -> 10

Le point ou l'on se trompe le plus souvent est 3 * 4 ** 2 : la puissance s'applique a 4 seul, pas a 3 * 4. Le resultat est 48 et non 144.

Notez au passage que ** s'associe a droite : 2 ** 3 ** 2 vaut 2 ** 9 = 512, et non 8 ** 2 = 64.

Multiplier du texte

Qu'affiche ce programme ?

s = "ab"
s = s * 3
print(s)
print(len(s))
print("-" * 20)
print("5" * 3)

La derniere ligne est un classique des pieges d'examen.

Rappel

L'operateur * entre une chaine et un entier repete la chaine. Il ne fait jamais de multiplication numerique, meme si la chaine ne contient que des chiffres.

Les chaines etant non modifiables, s * 3 fabrique une nouvelle chaine ; s = ne fait que rebrancher le nom dessus.

Voir le corrigéMasquer le corrigé

Corrigé

ababab
6
--------------------
555

"ab" * 3 donne "ababab", six caracteres.

"-" * 20 est la facon habituelle de tracer une ligne de separation : bien plus court que d'ecrire vingt tirets a la main, et plus facile a ajuster.

La derniere ligne affiche 555 et non 15. Ici "5" est du texte : il est repete trois fois. Pour obtenir 15, il fallait convertir : int("5") * 3.

Enfin, "ab" * 0 donne la chaine vide, et un facteur negatif aussi : aucune erreur n'est levee.

Questions / Réponses

Aucune question. Soyez le premier à poser une question.
★ ★ ★ ★ ★
Aucune note. Soyez le premier à attribuer une note !

Ajouter un commentaire

Anti-spam