Partie I - Fondamentaux Python · Chapitre 1

Notions de base

6 sections de cours · fiche d'exercices corrigee

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.

Definition — Algorithme et programme

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 :

CompilationInterpretation
Principetout le source est traduit une fois, en un executable chaque instruction est lue puis executee a la volee
Vitesse d'executioneleveeplus faible (facteur 10 a 100)
Detection des erreursa la compilation, avant toute execution a l'execution, seulement si la ligne est atteinte
ExemplesC, C++, Fortran, RustPython, 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

Piege classique

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

Definition — Variable

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 :

  1. l'expression de droite est evaluee, ce qui produit un objet ;
  2. 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
A retenir — l'echange en une ligne

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

Piege

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

TypeNom PythonExemplesRemarque
Entierint0, -17, 2**100precision illimitee en Python
Flottantfloat3.14, 1e-9, -0.5norme IEEE 754 sur 64 bits, environ 16 chiffres significatifs
BooleenboolTrue, False sous-type de int : True + True vaut 2
Chainestr"MPSI", '' sequence immuable de caracteres
RienNoneTypeNone 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'>
Entiers exacts, flottants approches
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.

Variables et objets : ce que fait vraiment l'affectation
Choisir un programme puis derouler instruction par instruction.

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

OperateurOperationExempleType du resultat
+ - *somme, difference, produit3 * 4 → 12 int si les deux operandes le sont
/division « vraie »7 / 2 → 3.5 toujours float
//quotient euclidien7 // 2 → 3 int
%reste euclidien7 % 2 → 1 int
**puissance2 ** 10 → 1024 int ou float
Propriete — division euclidienne en Python

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
Consequence utile

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 :

  1. ** (associatif a droite)
  2. +x, -x unaires
  3. *, /, //, %
  4. +, - binaires
  5. comparaisons < <= > >= == !=
  6. not, puis and, puis or
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
Evaluation paresseuse

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

Point crucial

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-langageEffet
:.nfflottant, n chiffres apres la virgule
:.nenotation scientifique
:dentier decimal
:< :^ :> aligne a gauche / centre / a droite
:,separateur de milliers
{{ }}accolade litterale
Application — une table de valeurs propre
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.2000

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

Regle

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
Erreurs les plus frequentes
  • 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.
Une indentation change tout
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

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")
Methode — ecrire un programme qui marche
  1. Specifier : que recoit la fonction, que renvoie-t-elle, sous quelles preconditions ? On l'ecrit en docstring avant de coder.
  2. Decouper : une fonction = une idee. Ici, le discriminant est isole car il a un sens propre et se teste separement.
  3. Traiter les cas limites d'abord (d < 0, d == 0) puis le cas general : le code devient lineaire, sans else imbriques.
  4. Tester avec assert, y compris les cas degeneres. Un assert qui echoue leve AssertionError et localise la faute.

Lire un message d'erreur

ExceptionCause typique
SyntaxErrorparenthese non fermee, : oublie, = au lieu de ==
NameErrorvariable utilisee avant d'etre affectee, ou faute de frappe dans un nom
TypeErroroperation entre types incompatibles ("3" + 4), mauvais nombre d'arguments
ValueErrorbon type, valeur impossible : int("abc"), sqrt(-1)
ZeroDivisionErrordivision ou modulo par zero
IndexErrorindice hors des bornes d'une sequence
IndentationErrorbloc 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, x echange en une ligne.
  • int est exact et non borne ; float est 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.
  • input renvoie 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/or sont court-circuitants.
Fiche d'exercices — chapitre 1Enonces gradues, du calcul d'application au niveau concours, avec corriges detailles.
Ouvrir la fiche →