1Programme, algorithme et interpreteur
Un programme est une suite finie d'instructions non ambigues, ecrite dans un langage formel, et destinee a etre executee par une machine. La difference avec une recette de cuisine est essentielle : la machine n'interprete rien, ne devine rien et ne corrige aucune imprecision. Tout ce qui n'est pas dit explicitement n'existe pas.
Un algorithme est une methode de resolution : une suite finie d'operations elementaires qui, a partir de donnees d'entree, produit un resultat en un nombre fini d'etapes. Un programme est la traduction d'un algorithme dans un langage de programmation donne. Un meme algorithme admet donc une infinite de programmes.
Interprete, compilateur, et le cas de Python
Le processeur n'execute que du langage machine (des suites d'octets). Pour passer du texte source a ce langage, deux strategies existent :
| Compilation | Interpretation | |
|---|---|---|
| Principe | tout le source est traduit une fois, en un executable | chaque instruction est lue puis executee a la volee |
| Vitesse d'execution | elevee | plus faible (facteur 10 a 100) |
| Detection des erreurs | a la compilation, avant toute execution | a l'execution, seulement si la ligne est atteinte |
| Exemples | C, C++, Fortran, Rust | Python, Ruby, JavaScript |
Python est un langage interprete : le fichier .py est lu ligne
apres ligne par l'interpreteur (CPython, en pratique). Consequence pratique et importante en
CPGE : une erreur de syntaxe dans une fonction jamais appelee ne sera signalee qu'a la
lecture du fichier, mais une erreur de type ne le sera qu'au moment ou la ligne fautive
s'execute.
def f(x):
return x + "a" # incoherent, mais Python ne dit rien...
print("le programme demarre")
f(3) # ... jusqu'ici : TypeError
Les deux modes de travail
- Mode interactif (la console, invite
>>>) : on tape une expression, elle est evaluee et sa valeur est affichee immediatement. Ideal pour explorer, tester une fonction, verifier un type. - Mode script : on ecrit un fichier
.pyexecute d'un bloc. Rien ne s'affiche sauf si on le demande parprint. C'est le mode des devoirs et des concours.
En mode interactif, 2 + 3 affiche 5. Dans un script, la meme ligne
n'affiche rien : la valeur est calculee puis jetee. Il faut ecrire
print(2 + 3). De meme, une fonction qui se termine par print(r) au
lieu de return r ne renvoie rien (elle renvoie None) et devient
inutilisable dans un calcul.
Anatomie d'un premier programme
# perimetre.py — calcule le perimetre et l'aire d'un rectangle
longueur = 6.0 # instruction d'affectation
largeur = 4.0
perimetre = 2 * (longueur + largeur)
aire = longueur * largeur
print("Perimetre :", perimetre)
print("Aire :", aire)
Perimetre : 20.0
Aire : 24.0
Quatre elements y figurent deja : des commentaires (apres #),
des variables, des expressions arithmetiques et des
appels de fonction. Le reste du chapitre precise chacun d'eux.
2Variables, objets et types de base
En Python, une variable est un nom qui refere a un objet en memoire. Elle n'est ni une case memoire de taille fixe, ni un type declare : c'est une etiquette que l'on colle sur une valeur, et que l'on peut recoller ailleurs a tout moment.
L'affectation
L'instruction nom = expression se lit de droite a gauche :
- l'expression de droite est evaluee, ce qui produit un objet ;
- le nom de gauche est attache a cet objet.
Le symbole = n'est donc pas l'egalite mathematique. L'instruction
n = n + 1, absurde en mathematiques, est parfaitement licite : elle signifie
« calculer n+1, puis faire pointer n sur ce resultat ».
n = 5
n = n + 1 # n vaut maintenant 6
x, y = 3, 7 # affectation multiple : x vaut 3, y vaut 7
x, y = y, x # echange, sans variable temporaire : x=7, y=3
x, y = y, x fonctionne parce que le membre de droite est entierement
evalue (Python construit le tuple (y, x)) avant toute affectation.
C'est une ecriture idiomatique attendue dans les algorithmes de tri.
Regles de nommage
- lettres, chiffres et
_; jamais de chiffre en premiere position ; - sensible a la casse :
Vitesseetvitessesont deux variables ; - les 35 mots-cles du langage sont interdits (
if,for,lambda,class...) ; - convention :
minuscules_avec_underscorespour les variables et fonctions,MAJUSCULESpour les constantes.
Nommer une variable list, sum, max ou str
est autorise mais masque la fonction native pour toute la suite du programme.
Apres max = 12, l'appel max(a, b) declenche
TypeError: 'int' object is not callable.
Les types de base
| Type | Nom Python | Exemples | Remarque |
|---|---|---|---|
| Entier | int | 0, -17,
2**100 | precision illimitee en Python |
| Flottant | float | 3.14, 1e-9,
-0.5 | norme IEEE 754 sur 64 bits, environ 16 chiffres significatifs |
| Booleen | bool | True, False |
sous-type de int : True + True vaut 2 |
| Chaine | str | "MPSI", '' |
sequence immuable de caracteres |
| Rien | NoneType | None |
valeur renvoyee par une fonction sans return |
La fonction type renseigne sur le type d'un objet ; c'est le premier reflexe de
debogage.
type(7) # <class 'int'>
type(7.0) # <class 'float'>
type(7 / 1) # <class 'float'> : / produit TOUJOURS un flottant
type(7 // 1) # <class 'int'>
2**200 # 1606938044258990275541962092341162602522202993782792835301376 (exact)
0.1 + 0.2 # 0.30000000000000004 (approche)
0.1 + 0.2 == 0.3 # False !
Un entier Python n'est jamais tronque : il grandit tant que la memoire le permet. Un flottant,
lui, est un rationnel dyadique approchant le reel voulu. On ne teste donc jamais
l'egalite de deux flottants : on ecrit abs(a - b) < 1e-12.
Conversions explicites
int("42") # 42 (chaine -> entier)
int(3.99) # 3 (troncature vers 0, PAS un arrondi)
int(-3.99) # -3
round(3.99) # 4 (arrondi, renvoie un int)
float(7) # 7.0
str(3.5) # '3.5'
bool(0), bool(""), bool([]) # (False, False, False)
bool(-2), bool("0") # (True, True)
Retenir la derniere ligne : est « faux » ce qui est nul ou vide ; tout le reste est
vrai, y compris la chaine "0" et le flottant -0.5.
3Operateurs, priorites et arithmetique entiere
Operateurs numeriques
| Operateur | Operation | Exemple | Type du resultat |
|---|---|---|---|
+ - * | somme, difference, produit | 3 * 4 → 12 |
int si les deux operandes le sont |
/ | division « vraie » | 7 / 2 → 3.5 |
toujours float |
// | quotient euclidien | 7 // 2 → 3 |
int |
% | reste euclidien | 7 % 2 → 1 |
int |
** | puissance | 2 ** 10 → 1024 |
int ou float |
Pour tout a entier et b entier non nul, Python garantit
\[ a = b \times (a \mathbin{/\!/} b) + (a \bmod b), \qquad
0 \le |a \bmod b| < |b|, \]
avec la convention que le reste a le signe du diviseur. Le quotient est arrondi
vers \(-\infty\), et non vers zero.
-7 // 2 # -4 (et non -3 : arrondi vers moins l'infini)
-7 % 2 # 1 (le reste a le signe de 2, donc positif)
7 % -2 # -1
Comme n % 2 vaut toujours 0 ou 1 meme pour n negatif, le test de
parite n % 2 == 0 est correct sur tout \(\mathbb{Z}\) — ce qui serait faux en C.
De meme k % n donne toujours un representant dans \(\{0, \dots, n-1\}\), ce qui
rend l'arithmetique modulaire agreable.
Priorites des operateurs
De la plus forte a la plus faible :
**(associatif a droite)+x,-xunaires*,/,//,%+,-binaires- comparaisons
< <= > >= == != not, puisand, puisor
2 ** 3 ** 2 # 512 = 2**(3**2), et non (2**3)**2 = 64
-2 ** 2 # -4 = -(2**2)
(-2) ** 2 # 4
10 - 3 - 2 # 5 (les - sont associatifs a gauche)
Affectations augmentees
s = 0
s += 5 # equivaut a s = s + 5
s *= 2 # s = s * 2
s //= 3 # s = s // 3
s **= 2 # s = s ** 2
Elles evitent de repeter le nom de la variable, ce qui reduit les fautes de frappe dans les
accumulateurs, tres frequents en algorithmique (somme += t[i]).
Comparaisons chainees et operateurs logiques
a = 5
0 <= a < 10 # True : notation mathematique valide en Python
0 <= a and a < 10 # equivalent, plus verbeux
and et or sont court-circuitants : dans
A and B, si A est faux, B n'est jamais evalue. Cela permet
d'ecrire des gardes sures :
if n != 0 and 100 / n > 5: # jamais de division par zero
...
L'ordre des conditions n'est donc pas commutatif en pratique : inverser les
deux tests ci-dessus provoquerait une ZeroDivisionError.
4Entrees, sorties et formatage
Sortie : print
print("a =", 3, "b =", 4) # a = 3 b = 4
print("a", "b", sep="-") # a-b
print("sans retour a la ligne", end="")
print(1, 2, 3, sep="\n") # un entier par ligne
Par defaut print insere une espace entre ses arguments (sep=" ") et
termine par un retour a la ligne (end="\n").
Entree : input
input() renvoie toujours une chaine de caracteres, meme si
l'utilisateur tape des chiffres. Il faut convertir explicitement.
n = input("Entrez un entier : ") # n est une CHAINE
n = int(n) # maintenant c'est un entier
# ou, en une ligne :
x = float(input("Entrez un reel : "))
n = input("n ? ") # l'utilisateur tape 3
n * 2 # '33' et non 6 : concatenation de chaines !
Formatage : les f-strings
Depuis Python 3.6, on prefixe la chaine par f et l'on place les expressions
entre accolades. C'est la methode a privilegier.
nom, note = "Amine", 17.5
print(f"{nom} a obtenu {note}/20")
print(f"Moyenne : {note/2:.2f}") # 2 decimales -> 8.75
print(f"{3.14159:8.3f}|") # largeur 8 -> 3.142|
print(f"{42:05d}") # zeros a gauche-> 00042
print(f"{0.0000123:.3e}") # notation sci. -> 1.230e-05
print(f"{255:b} {255:o} {255:x}") # bases 2, 8, 16-> 11111111 377 ff
print(f"{'MPSI':>10}|") # aligne a droite
| Mini-langage | Effet |
|---|---|
:.nf | flottant, n chiffres apres la virgule |
:.ne | notation scientifique |
:d | entier decimal |
:< :^ :> |
aligne a gauche / centre / a droite |
:, | separateur de milliers |
{{ }} | accolade litterale |
for k in range(1, 6):
print(f"{k:>3} | {k**2:>5} | {1/k:>8.4f}")
1 | 1 | 1.0000
2 | 4 | 0.5000
3 | 9 | 0.3333
4 | 16 | 0.2500
5 | 25 | 0.2000Commentaires et docstrings
def moyenne(t):
"""Renvoie la moyenne arithmetique de la liste non vide t.
Precondition : t est une liste non vide de nombres.
"""
return sum(t) / len(t) # somme / effectif
help(moyenne) # affiche la docstring
Un commentaire (#) explique le pourquoi, jamais le quoi : la
ligne i = i + 1 # on ajoute 1 a i est inutile. Une docstring
(chaine placee en premiere instruction d'une fonction) documente le contrat de la fonction :
c'est la forme attendue dans les copies de concours pour enoncer la specification.
5Instructions, blocs et indentation
Python n'utilise ni accolades ni begin/end : c'est l'indentation
qui delimite les blocs. Elle fait partie de la syntaxe.
Une ligne se terminant par : ouvre un bloc. Toutes les instructions de ce bloc
sont indentees du meme nombre d'espaces (convention : 4 espaces). Le bloc se
termine des que l'indentation revient au niveau precedent.
temperature = 38.5
if temperature > 37.5:
print("Fievre") # bloc du if
print("Consulter") # meme bloc
print("Fin") # hors du if : toujours execute
IndentationError: expected an indented block: un:n'est pas suivi d'un bloc indente.TabError: melange de tabulations et d'espaces. Ne jamais melanger ; configurer l'editeur pour convertir la tabulation en 4 espaces.- Une instruction sortie du bloc par erreur : le programme s'execute sans message d'erreur mais donne un resultat faux. C'est le plus dangereux.
s = 0
for k in range(1, 5):
s += k
print(s) # affiche 1, 3, 6, 10 : DANS la boucle
s = 0
for k in range(1, 5):
s += k
print(s) # affiche 10 : APRES la boucle
Meme code a un espace pres, comportement radicalement different. Relire l'indentation est le premier reflexe quand un resultat est inattendu.
Instructions simples, instructions composees
- simple : tient sur une ligne (affectation, appel,
return,pass) ; - composee : en-tete terminee par
:puis un bloc (if,for,while,def).
Le mot-cle pass est l'instruction qui ne fait rien ; il sert a construire un bloc
syntaxiquement valide mais vide, le temps d'ecrire le reste.
Lignes longues
# continuation implicite (recommandee) : a l'interieur de ( ) [ ] { }
total = (prix_unitaire * quantite
+ frais_de_port
- remise)
# continuation explicite : anti-slash en fin de ligne (a eviter)
total = prix_unitaire * quantite \
+ frais_de_port
6Ecrire, structurer et deboguer un programme
Voici la structure type d'un programme complet, telle qu'attendue dans un devoir : une specification claire, des fonctions courtes, un jeu de tests.
Etude de cas — resolution d'une equation du second degre
from math import sqrt
def discriminant(a, b, c):
"""Renvoie le discriminant de a x^2 + b x + c.
Precondition : a, b, c sont des nombres."""
return b*b - 4*a*c
def racines(a, b, c):
"""Renvoie le tuple des racines reelles de a x^2 + b x + c (a != 0).
Renvoie () si aucune racine reelle, (r,) si racine double,
(r1, r2) avec r1 < r2 sinon.
"""
d = discriminant(a, b, c)
if d < 0:
return ()
if d == 0:
return (-b / (2*a),)
r = sqrt(d)
return ((-b - r) / (2*a), (-b + r) / (2*a))
# --- jeu de tests -------------------------------------------------
assert racines(1, -3, 2) == (1.0, 2.0)
assert racines(1, 2, 1) == (-1.0,)
assert racines(1, 0, 1) == ()
print("tests ok")
- Specifier : que recoit la fonction, que renvoie-t-elle, sous quelles preconditions ? On l'ecrit en docstring avant de coder.
- Decouper : une fonction = une idee. Ici, le discriminant est isole car il a un sens propre et se teste separement.
- Traiter les cas limites d'abord (
d < 0,d == 0) puis le cas general : le code devient lineaire, sanselseimbriques. - Tester avec
assert, y compris les cas degeneres. Unassertqui echoue leveAssertionErroret localise la faute.
Lire un message d'erreur
| Exception | Cause typique |
|---|---|
SyntaxError | parenthese non fermee, : oublie,
= au lieu de == |
NameError | variable utilisee avant d'etre affectee, ou faute de frappe dans un nom |
TypeError | operation entre types incompatibles
("3" + 4), mauvais nombre d'arguments |
ValueError | bon type, valeur impossible : int("abc"),
sqrt(-1) |
ZeroDivisionError | division ou modulo par zero |
IndexError | indice hors des bornes d'une sequence |
IndentationError | bloc mal aligne |
Le message se lit de bas en haut : la derniere ligne donne le type et la raison, l'avant-derniere la ligne fautive.
L'essentiel du chapitre
- Une variable est un nom attache a un objet ;
=est une affectation, pas une egalite.x, y = y, xechange en une ligne. intest exact et non borne ;floatest approche : on ne teste jamais l'egalite de deux flottants./donne un flottant,//et%donnent des entiers, avec un reste du signe du diviseur.**est associatif a droite et plus prioritaire que le moins unaire.inputrenvoie une chaine : convertir systematiquement.- Les f-strings
f"{x:.3f}"sont le moyen standard de formater une sortie. - L'indentation est la syntaxe des blocs ;
and/orsont court-circuitants.