Cours Python
Cours Python
1 Chapitre 1 - Introduction 3
6 Chapitre 6 - Listes 35
8 Chapitre 8 - Dictionnaires 47
9 Chapitre 9 - Tuples 53
13 Chapitre 13 - Bibliothèques 73
16 Chapitre 16 - Héritage 99
i
ii
Programmation en Python
Ce cours est destiné à toute personne qui voudrait découvrir la programmation en général et le langage Python en
particulier - il est également disponible en pdf à l’adresse : https://dmerej.info/python/cours-python.pdf
Le pdf comme le site sont régulièrement mis à jour, et les sources sont disponibles publiquement sur sourcehut.
J’espère qu’il vous plaira ! Sinon, n’hésitez pas à vous rendre sur ma page de contact pour me faire part de vos
remarques.
Enfin, notez que ce cours est placé sous licence CC BY 4.0, ce qui signifie que vous pouvez le partager et le modifier
comme bon vous semble, à la seule condition de me créditer de façon appropriée.
Bonne lecture !
Contents 1
Programmation en Python
2 Contents
CHAPTER 1
Chapitre 1 - Introduction
• Excellent langage pour débuter - il a d’ailleurs été créé avec cet objectif en tête.
• Mon langage préféré.
• Facile à installer quelque soit le système d’exploitation (Linux, macOS, Windows).
C’est une interface universelle. Pour faire des programmes en lignes de commande, on a juste besoin de manipuler du
texte, et c’est le plus simple au début.
Il est important de noter que les chapitres sont prévus pour être lus dans l’ordre.
Par exemple, pour bien comprendre le chapitre 10 sur les classes, il faut avoir bien compris les chapitres précédents,
car les explications du chapitre 10 supposent que les notions introduises aux chapitres 1 à 9 sont acquises !
Il est en conséquence recommandé d’arriver à résoudre les exercices en fin de chapitre avant de passer au suivant.
3
Programmation en Python
1.2.3 Historique
1.2.5 Python3
• Très puissant.
• Ancien, mais toujours d’actualité.
• Indispensable dans de nombreux cas.
• Écrire des programmes qui fonctionnent dans la ligne de commande est (relativement) simple.
• Possibilités infines, même si on ne fait que manipuler du texte.
Notez le petit bout de texte avant le curseur : on appelle ça une invite de commande (prompt en anglais).
7
Programmation en Python
Pour lancer une commande, on tape son nom, suivi parfois d’un certain nombre de mots séparés par des espaces, puis
on appuie sur entrée.
Par exemple, on peut utiliser ls (ou dir sous Windows) pour lister le contenu du répertoire courant :
Et on peut utiliser cd suivi du nom d’un répertoire pour changer de répertoire courant :
2.2 Installation
Linux
Il y a toutes les chances que Python soit déjà installé sur votre distribution. Pour vous en assurer, tapez :
python3 --version
Sinon, installez le paquet correspondant à Python3 (quelque chose comme sudo apt install python3)
macOS
Windows
Je vous conseille pour commencer d’utiliser un éditeur de texte basique. Vous n’avez pas besoin d’un IDE, surtout si
vous débutez
• Linux; gedit, kate, . . . ;
• macOS: CotEditor ;
• Windows: Notepad++.
J’insiste sur simple. Inutile d’installer un IDE pour le moment.
2.3.1 Définition
print("Bonjour, monde")
# affiche: Bonjour, monde
cd Documents/python/
python3 bonjour.py
Bonjour, monde
Vous savez maintenant comment exécuter du code Python dans n’importe quel fichier :
1. Écrire le code dans un fichier ;
2. Se rendre dans le répertoire contenant le fichier et lancer python3 (ou python) suivi du nom du fichier.
2.3.4 print()
Revenons sur ce qu’il s’est passé : nous avions le mot print avec des parenthèses et quelque chose à l’intérieur des
parenthèses, et ceci a provoqué l’affichage du contenu des parenthèses dans le terminal.
2.3.5 Commentaires
La deuxième ligne, quant à elle, a été complètement ignorée par l’interpréteur parce qu’elle commençait par un #. Il
s’agit d’un commentaire, et il sert principalement aux humains qui lisent le code.
La plupart des exemples de ce cours contiendront un ou plusieurs appels à print afin d’afficher les opérations que
l’interpréteur a effectué.
Pour lire ce cours de manière efficace, il est conseillé de lancer les exemples de code sur votre machine, et de vérifier
si ce qui est affiché sur votre machine correspond à ce qui est écrit dans le cours.
Il est aussi recommandé de ne pas copier/coller le code.
À la place, prenez le temps de retaper le code dans votre éditeur.
Plusieurs raisons à cela:
• Recopier le code vous aidera à vous souvenir de la syntaxe
• Si vous faites des erreurs, Python vous préviendra et vous découvrirez les erreurs courantes
• Il est possible que des erreurs subsistent dans ce cours, et procéder ainsi nous permettra de les corriger.
print(1 + 2)
# affiche: 3
print(6 - 3)
# affiche: 3
print(0.5 + 0.2)
# affiche: 0.7
print(10 / 2)
# affiche: 5.0 (et non '5')
Notez que les flottants sont imprécis, ce qui explique le 5 à la fin de l’affichage de la division de 10 par 3 :
print(10 / 3)
# affiche:3.3333333333333335
print(14 // 3)
# affiche: 4
print(14 % 3)
# affiche: 2
print(1 + 2 * 3)
# affiche: 7
print((1 + 2) * 3)
# affiche: 9
3.1.1 Instructions
Pour l’instant, dans tous les exemples de code, chaque ligne qu’on a écrit contenait une instruction.
Un instruction a un effet sur le programme dans lequel elle est présente.
Par exemple, l’instruction print("bonjour") affiche « bonjour » dans le terminal. On dit que l’instruction est
exécutée.
En règle générale, les instructions sont exécutées une par une, de haut en bas.
3.1.2 Expressions
1 + (2 * 3)
13
Programmation en Python
À droite du plus, on a une expression 2 + 3. Quand Python évaluera l’expression, il verra d’abord le littéral 1 et le +,
puis il évaluera l’expression à droite (2 * 3 = 6), et finalement l’expression en entier (1 + 6 = 7).
Notez que si vous écrivez une ligne contenant une expression, elle est évaluée mais rien n’est exécuté :
print("Bonjour")
40 + 2
print("Au revoir")
# Affiche: 'Bonjour' puis 'Au revoir'
Ici l’expression 40 + 2 a été évaluée, mais Python n’a rien fait avec le résulat, il est simplement passé à l’instruction
suivante.
On peut associer des variables à des valeurs en les plaçant de part et d’autre du signe = : on appelle cette opération
une assignation :
a = 2
a = 2
print(a)
# affiche: 2
En fait, on peut assigner n’importe qu’elle expression à une variable, et pas simplement des littéraux :
a = 1 + 2
print(a)
# affiche: 3
a = 1
print(a + 2) # ici "a" a été remplacé par 1
# affiche: 3
a = 1
print(a + 2)
# affiche: 3
print(a)
# affiche: 1
Autres exemples :
x = 1
y = 2
print(x+y) # ici x a été remplacé par 1 et y par 2
# affiche: 3
On peut aussi changer la valeur d’une variable en assignant une nouvelle valeur à celle-ci :
a = 2
print(a)
a = 3
print(a)
# affiche: 2, puis 3
La notation += permet de combiner addition et assignation : les deux exemples ci-dessous sont équivalents :
x = 3
x = x + 1
x = 3
x += 1
Ci-dessus j’ai utilisé des noms de variables à une lettre, mais il est préférable d’avoir des noms longs et descriptifs.
Aussi, la convention est de :
• les écrire en minuscules ;
• de séparer les mots par des tirets bas (underscore) :
score = 42
âge_moyen = 22
On dit qu’on utilise le snake case (parce que ça ressemble vaguement à un serpent)
Notez que certains mots ne peuvent pas être utilisés comme nom de variables. On les appelle des mots-clés. La liste
est disponible ici: https://docs.python.org/fr/3/reference/lexical_analysis.html#keywords
Les chaînes de caractères, aussi appelées « strings », permettent de représenter du texte. On a utilisé une string pour
afficher « bonjour monde » dans le chapitre précédent.
On écrit toujours les strings entre guillemets (ou quotes).
soit avec des doubles guillemets :
print("Bonjour monde!")
# affiche: Bonjour monde!
print('Bonjour monde!')
# affiche: Bonjour monde!
On peut mettre des simples quotes dans des double quotes et vice-versa :
3.2.2 Échappement
On peut aussi échapper des caractères avec la barre oblique inversée \\ - backslash.
3.2.3 Concaténation
On peut construire de longues chaînes de caractères en en concaténatant de plus petites, avec l’opérateur + :
name = "John"
message = "Bonjour " + name + " !"
print(message)
# affiche: Bonjour John !
3.2.4 Répétition
On peut construire une longue string en répétant la même petite string plusieurs fois avec l’opérateur * :
message = "Na" * 3
print(message)
# affiche: NaNaNa
3.3 Types
On a vu qu’on pouvait utiliser + à la fois pour additionner des nombres ou concaténer des strings. Mais on ne peut pas
utiliser + avec une string d’un côté et un entier de l’autre :
a = 42
b = 4
c = a + b # ok
résultat = a + prénom
# affiche:
# TypeError: can only concatenate str (not "int") to str
Ceci est notre premier message d’erreur : si l’interpréteur est incapable d’exécuter une instruction, il affiche un message
d’erreur et s’interrompt immédiatement.
3.3.1 Conversions
On peut convertir un entier en string en utilisant le mot str et des parenthèses autour de l’expression :
x = 40
y = 2
message = "La réponse est: " + str(x + y)
print(message)
# affiche: La réponse est 42
3.3. Types 17
Programmation en Python
Inversement, on peut convertir un string en entier en utilisant le mot int et des parenthèses :
quarante_en_chiffres = "40"
réponse = int(quarante_en_chiffres) + 2
print(réponse)
# affiche: 42
taille_sur_le_passeport = "1.62"
taille_en_mètres = float(taille_sur_le_passeport)
3.4 Booléens
On appelle booléenne une valeur qui peut être soit vraie, soit fausse.
En Python, les littéraux True et False représentent respectivement les valeurs vraies et fausses.
(Notez qu’ils commencent par une majuscule)
3.4.1 Comparaisons
Certaines expressions renvoient des booléens, c’est à dire soit la valeur True, soit la valeur False
== égal
!= différent
> strictement supérieur
>= supérieur ou égal
< strictement inférieur
<= inférieur ou égal
Par exemple:
a = 2
b = 3
print(a > b)
# affiche: False
print(2 + 2 == 4)
# affiche: True
Notez que deux valeurs avec des types différents sont en général considérés comme différents :
1 == "1" # False
Cette règle admet des exceptions, par exemple un flottant peut être égal à un entier :
1 == 1.0 # True
not négation
and et
or ou
Exemples :
a = not True
print(a)
# affiche `False`
il_pleut = True
j_ai_un_parapluie = False
print(il_pleut and j_ai_un_parapluie)
# affiche: False
3.4. Booléens 19
Programmation en Python
Pour l’instant, toutes les instructions que nous avons écrites ont été exécutée une par une et dans l’ordre d’apparition
dans le code source.
De plus, chaque ligne était constituée d’une unique expression.
Modifier l’ordre d’exécution de ces instructions s’appelle le contrôle de flux, et c’est l’essence même de la
programmation !
4.1.1 if
On peut utiliser le mot-clé if pour autoriser ou empêcher l’exécution des instructions suivantes :
a = 3
b = 4
if a == b:
print("a et b sont égaux")
# n'affiche rien
21
Programmation en Python
if a = 3:
print("a égale 3")
# affiche: SyntaxError
Notez qu’on reprend l’ordre habituel d’exécution des instructions s’il y a un bloc indenté dans l’autre sens après le if :
a = 3
b = 4
if a == b:
# début du bloc
print("a et b sont égaux")
c = 2 * a
# fin du bloc
# on est après le bloc if
print("on continue")
if / else
On peut utiliser le mot-clé else après un condition en if pour exécuter un bloc si la condition est fausse :
a = 3
b = 4
if a == b:
print("a et b sont égaux")
else:
print("a et b sont différent")
# affiche: a et b sont différents
if / elif
while
On peut utiliser le mot-clé while pour répéter un bloc tant qu’une condition est vraie :
i = 0
while i < 3:
print(i)
i = i + 1
0
1
2
Boucle infinie
while True:
print("spam!")
i = 0
while True:
i = i + 1
print(i)
if i > 3:
break
1
2
3
4
Jusqu’ici les expressions qu’on a utilisé donnait un booléen une fois évaluées, mais une expression après un if peut
être d’un autre type.
Par exemple, un entier :
x = 0
if x:
print("x n'est pas nul")
else:
print("x est nul")
On dit que 0 est Falsy, parce qu’après un if, il se comporte comme une expression qui vaudrait False.
Réciproquement, tous les entiers sauf 0 sont Truthy, parce qu’ils se comportent comme une expression qui vaudrait
True :
y = 6
if y:
print("y n'est pas nul")
else:
print("y est nul")
message = ""
if message:
print("le message n'est pas vide")
else:
print("le message est vide")
# affiche: le message est vide
En fait, on peut utiliser tous les opérateurs booléens avec des expressions quelconques :
message = ""
if not message:
print("le message est vide")
# affiche: le message est vide
score = 42
if message and score:
(suite sur la page suivante)
4.3 Exercice
4.3.1 Consignes
4.3.2 Indices
Pour bloquer le programme et lire une entrée utilisateur, vous pouvez utiliser la ligne suivante :
entrée_utilisateur = input()
Cette instruction va :
• interrompre le script ;
• lire ce que l’utilisateur tape jusqu’à ce qu’il tape « entrée » ;
• et assigner la valeur correspondante à la variable entrée_utilisateur.
4.3. Exercice 25
Programmation en Python
Pour tirer un nombre au hasard entre 1 et 100, vous pouvez utiliser les deux lignes suivantes :
import random
nombre_secret = random.randint(0, 100)
À la fin de ces deux instructions, une valeur entre 1 et 100 tirée au hasard sera assignée à la variable nombre_secret.
4.3.3 Squelette
Pour commencer, copier le code suivant dans un fichier (par exemple, devine-nombre.py)
import random
while True:
if entrée_utilisateur == nombre_secret:
print("bravo")
break
else:
print("mauvaise réponse")
entrée_utilisateur = int(input())
Si vous lancez python3 devine-nombre.py vous constaterez que le jeu est très difficile parce que le programme affiche
simplement « mauvaise réponse » en boucle jusqu’à ce que l’utilisateur devine le nombre secret.
Votre but est de modifier le code pour implémenter correctement le programme, et ainsi rendre le jeu jouable :)
Si vous lisez ceci en dehors d’un cours et que vous êtes coincés, n’hésitez pas à me contacter.
Bon courage !
5.1 Fonctions
Définition:
def dire_bonjour():
print("Bonjour")
Notez qu’on utilise aussi le snake case pour les noms de fonctions, comme pour les noms de variables.
• avec le mot-clé def ;
• avec un : à la fin et un bloc indenté (appelé le corps de la fonction).
Appel :
dire_bonjour()
def dire_bonjour():
print("Bonjour")
dire_bonjour()
# Affiche: bonjour'
27
Programmation en Python
Ici on vient d’ajouter une nouvelle fonctionnalité à Python. Avant qu’on définisse la fonction dire_bonjour(), il ne savait
pas dire bonjour, il savait uniquement afficher des messages à l’écran.
On dit qu’on a créé une abstraction. Et c’est une technique extrêmement utile en programmation.
def dire_bonjour(prénom):
print("Bonjour " + prénom)
dire_bonjour("Germaine")
# Ceci:
dire_bonjour("Dimitri")
# Lui-même équivalent à:
print("Bonjour " + "Dimitri")
Exemple complet :
def dire_bonjour(prénom):
print("Bonjour " + prénom)
dire_bonjour("Germaine")
# affiche: Bonjour Germaine
prénom_de_charlotte = "Charlotte"
dire_bonjour(prénom_de_charlotte)
# affiche: Bonjour Charlotte
def dire_bonjour(prénom):
print("Bonjour " + prénom)
dire_bonjour("Dimitri") # Ok
print(prénom) # Erreur
Les variables définies en dehors du corps des fonctions sont disponibles partout :
def dire_bonjour(prénom):
print(salutation + prénom)
dire_bonjour("Dimitri")
Une variable peut avoir en « cacher » une autre si elle a une portée différente :
def dire_bonjour():
prénom = Max # portée: uniquement dans
# le corps dire_bonjour
On peut également appeler une fonction dans une expression à droite d’une assignation de variable.
Dans ce cas, la valeur de l’expression est obtenue en exécutant le corps de la fonction jusqu’à rencontrer l’instruction
return et en évaluant l’expression à droite du return.
Par exemple:
def retourne_42():
return 42
x = retourne_42()
print(x)
# Affiche: 42
def peut_conduire(âge):
if âge < 18:
return False
else:
return True
x = peut_conduire(16)
print(x)
# Affiche: False
On peut mettre autant d’arguments qu’on veut, séparés par des virgules :
résultat = soustraction(5, 4)
print(résultat)
# affiche: 1
En Python, on peut aussi utiliser le nom des arguments au lieu de leur position :
def dire_bonjour(prénom):
print("Bonjour " + prénom)
dire_bonjour(prénom="Gertrude")
# Affiche: Bonjour Gertrude
Appel :
dire_bonjour("Thomas")
# affiche: Bonjour Thomas
dire_bonjour("Thomas", enthousiaste=True)
# affiche: Bonjour Thomas!
dire_bonjour("Thomas", enthousiaste=False)
# affiche: Bonjour Thomas
Fonctions qui sont toujours présentes dans l’interpréteur. On en a déjà vu quelques unes :
• print(), input() : écrire et lire sur la ligne de commande ;
• str(), int() : convertir des entiers en strings et vice-versa.
Il y en a tout un tas !
La liste ici : https://docs.python.org/fr/3/library/functions.html
prénom = "Charlotte"
print("Bonjour", pŕenom)
print("Ça va ?")
Bonjour Charlotte
Ça va ?
a = "chauve"
b = "souris"
print(a, b, sep="-")
chauve-souris
5.7 Exercice
Le but de l’exercice est d’afficher un sapin de largeur arbitraire dans la console, comme ceci :
#
###
#####
#######
#########
#
#
Le sapin est composé d’une suite de lignes, chacune des lignes étant constituée uniquement de dièses.
Il y a deux parties au sapin : les feuilles qui forment un triangle de largeur 1 tout en haut jusqu’à une ligne de largeur
9 tout en bas, et un pied constitué de deux dièses superposés
5.7.1 Indices
Pour construire une chaîne de caractères constituée uniquement de dièses vous pouvez utiliser l’expression suivante:
cinq_diéses = "#" * 5
print(cinq_diéses)
#####
5.7.2 Consignes
largeur = 9
def affiche_feuilles():
affiche_ligne(5, 6)
affiche_ligne(4, 7)
# à compléter
def affiche_pied():
affiche_ligne(5, 6)
# à compléter
affiche_sapin()
5.7. Exercice 33
Programmation en Python
Chapitre 6 - Listes
6.1.1 Définition
liste_vide = []
trois_entiers = [1, 2, 3]
35
Programmation en Python
liste_vide = []
taille = len(liste_vide)
print(taille)
# affiche: 0
trois_entiers = [1, 2, 3]
taille = len(trois_entiers)
print(taille)
# affiche: 3
Avec + :
print(prénoms)
# affiche: ['Alice', 'Bob', "Charlie", 'Eve']
scores = [1, 2, 3]
scores = scores + 4
# erreur
scores = [1, 2, 3]
scores = scores + [4]
print(scores)
# affiche: [1,2,3,4]
On peut aussi utiliser += pour combiner l’assignation et la concaténation, comme avec les nombres :
a = 3
a += 2
print(a)
# Affiche: 5
x = [1, 2]
x += [3, 4, 5]
print(x)
# Affiche: [1, 2, 3, 4, 5]
On peut récupérer un élément d’une liste à partir de son index, en utilisant [i] où i est l’index de l’élément.
Les index valides vont de 0 à n-1 où n est la taille de la liste :
print(fruits[0])
# affiche: "pomme"
print(fruits[1])
# affiche: "orange"
print(fruits[2])
# affiche: "poire"
fruits[3]
# erreur: IndexError
6.2 Itération
Bonjour Alice
Bonjour Bob
Bonjour Charlie
6.2. Itération 37
Programmation en Python
6.2.2 break
Comme pour les boucles while, on peut interrompre la boucle for avec break :
Bonjour Alice
Bonjour Bob
6.2.3 continue
On peut interrompre l’exécution du bloc courant (et uniquement le bloc courant) avec le mot-clé continue :
Bonjour Alice
Bonjour Charlie
Si on met une liste vide, if se comportera comme si on avait mis une valeur fausse, et si la liste n’est pas vide , if se
comportera comme si on avait mis une valeur vraie :
ma_liste = [1, 2, 3]
if ma_liste:
print("ma_liste est truthy")
else:
print("ma_liste est falsy")
# affiche: ma_liste est truthy
mon_autre_liste = []
if mon_autre_liste:
print("mon_autre_liste n'est pas vide")
else:
print("mon_autre_liste est vide")
# affiche: mon_autre_liste est vide
On dit que les listes vides sont Falsy et les listes non-vides Truthy.
On peut utiliser l’opérateur == avec deux listes de part et d’autre. Les listes seront considérées comme égales si :
• elles ont la même taille ;
• tous leurs éléments sont égaux un à un en respectant l’ordre :
x = [1]
y = [1, 2]
print(x == y)
# affiche: False, x et y n'ont pas la même taille
x = [1, 2]
y = [1, 3]
print(x == y)
# affiche: False, x et y n'ont pas les mêmes éléments
x = [1, 2]
y = [2, 1]
print(x == y)
# affiche: False, x et y ont les mêmes éléments, mais
# pas dans le bon ordre
x = [1, 2]
y = [1]
y += [2]
print(x == y)
# affiche: True, x et y ont les mêmes éléments, dans le même ordre
for c in "vache":
print(c)
v
a
c
h
e
print("e" in "vache")
# affiche: True
print("x" in "vache")
# affiche: False
Notez qu’on peut aussi utiliser in pour tester si une chaîne de caractères est contenue dans une autre :
print("ch" in "vache")
# affiche: True
prénom = "Charlotte"
l = prénom[0]
print(l)
# affiche: "C"
prénom = "Charlotte"
prénom[0] = "X"
# erreur: TypeError
6.5 Exercice
______
entrer une lettre
e
_____e
entrer une lettre
a
_a___e
entrer une lettre
u
_au__e
entrer une lettre
q
_au__e
entrer une lettre
(suite sur la page suivante)
6.5.1 Consignes
6.5. Exercice 41
Programmation en Python
7.1 None
7.1.1 Définition
En réalité, toutes les fonctions python retournent quelque chose, même quand elles ne contiennent pas le mot-clé
return.
def ne_renvoie_rien():
x = 2
resultat = ne_renvoie_rien()
print(resultat)
# affiche: None
43
Programmation en Python
La plupart des fonctions que nous avons vu échouent si on leur passe None en argument :
x = len(None)
# erreur: TypeError
x = None < 3
# erreur: TypeError
x = int(None)
# erreur: TypeError
x = str(None)
print(x)
# affiche: 'None'
x = ne_renvoie_rien()
print(x is None)
# affiche: True
7.2 pass
À cause de l’organisation du flot de contrôle en blocs indentés, on ne peut pas vraiment avoir de blocs vides. Mais
parfois, on a besoin d’un bloc qui ne fasse rien - c’est là que le mot-clé pass rentre en jeu.
pass représente une instruction qui ne fait rien.
Un exemple :
une_condition = False
if une_condition:
pass
else:
print("une_condition n'est pas vraie")
On peut aussi - et c’est l’usage le plus courant - utiliser pass pour définir une fonction qui ne fait rien :
def ne_fait_rien():
pass
7.2. pass 45
Programmation en Python
Chapitre 8 - Dictionnaires
8.1.1 Définition
dictionnaire_vide = {}
Ensuite, on utilise : pour séparer la clé et la valeur. Enfin, les paires de clé/valeur sont séparées par des virgules , :
47
Programmation en Python
Avec [], comme pour les listes, mais avec une clé à la place d’un index :
print(scores["john"])
# affiche: 10
print(scores["bob"])
# affiche: 42
print(scores["charlie"])
# erreur: KeyError
rappel : Ceci ne fonctionne pas avec les listes. On ne peut pas « créer » de nouveaux éléments dans la liste juste avec
un index :
ma_liste[3] = "d"
# erreur: IndexError
8.1.6 del
mon_entier = 42
mon_entier += 3
print(mon_entier)
# affiche: 45
del mon_entier
mon_entier += 1
# erreur: NameError
john 10
bob 42
Les dictionnaires vides sont falsy, et tous les autres dictionnaires sont truthy :
mon_autre_dico = {}
if mon_autre_dico:
print("mon_autre_dico n'est pas vide")
else:
print("mon_autre_dico est vide")
# affiche: mon_autre_dico est vide
On peut vérifier si une clé est présente dans un dictionnaire avec le mot clé in, un peu comme pour les listes :
print("charlie" in scores)
# affiche: False
Deux dictionnaires sont considérés égaux s’ils ont les mêmes clés et les mêmes valeurs. L’ordre n’a pas d’importance :
8.4 Exercice
8.4.1 Consignes
python convertisseur.py 1 km mi
Il y a trois « mots » après le nom du fichier, séparés par des espaces. On appelle ces mots les « arguments » du
programme.
Ici, 1 est la valeur de départ, km l’abréviation de l’unité de départ et mi l’abréviation de l’unité d’arrivée.
Voici un tableau pour vous aider :
8.4.2 Squelette
def trouver_coefficient(unité):
return 1.0
def main():
nombre_arguments = len(sys.argv) - 1
if nombre_arguments != 3:
print("3 arguments attendus, mais", nombre_arguments, "reçu(s)")
return
valeur = float(sys.argv[1])
unité_de_départ = sys.argv[2]
unité_d_arrivée = sys.argv[3]
valeur_de_sortie = convertir(valeur, unité_de_départ, unité_d_arrivée)
print("{:.6}".format(valeur_de_sortie))
(suite sur la page suivante)
8.4. Exercice 51
Programmation en Python
main()
Vous noterez que le programme est capable de récupérer la valeur, l’unité de départ et l’unité d’arrivée correctement,
mais que le résultat est toujours égal à 1.0.
Votre objectif est de finir l’implémentation pour réaliser effectivement la conversion.
À noter : vous n’avez pas besoin de changer l’implémentation des fonctions main() et convertir(), et ne vous
inquiétez pas si vous ne comprenez pas exactement ce que fait la fonction main(), on expliquera en détail comment
elle fonctionne dans un chapitre ultérieur. Pour terminer l’exercice, vous n’avez besoin que de modifier les fonctions
trouver_coefficient(), conversion_en_mètres() et conversion_depuis_mètres().
À vous de jouer !
Chapitre 9 - Tuples
9.1 Définition
Un tuple est un ensemble ordonné et immuable d’éléments (il est aussi appelé n-uplet dans la documentation Python).
Le nombre, l’ordre et la valeur des éléments sont fixes.
tuple_vide = ()
tuple_à_un_élement = (1,) # notez la virgule
tuple_à_deux_éléments = (1, 2) # on dit aussi: "couple"
Comme les listes, les tuples peuvent contenir des éléments de types différents :
53
Programmation en Python
9.4 Accès
print(mon_tuple[1])
# affiche: "bonjour"
9.5 Modification
C’est interdit :
Avec in :
print(14 in mon_tuple)
# affiche: True
print(13 in mon_tuple)
# affiche: False
9.7 Dissociation
print(side_kick)
# affiche: Robin
(héros,) = couple
# erreur: ValueError (1 != 2)
# Gare à la virgule:
héros, = couple
# erreur: ValueError (1 != 2)
9.9 Pièges
print(premier)
# affiche: pomme
print(deuxième)
# affiche: banane
print(troisième)
# affiche: orange
# Avant:
if (
ma_valeur == "nord" or
ma_valeur == "sud" or
ma_valeur == "ouest" or
ma_valeur == "est"):
print("direction", ma_valeur)
# Après:
if ma_valeur in ("nord", "sud", "est", "ouest"):
print("direction", ma_valeur)
def tire_carte():
valeur = "10"
couleur = "trèfle"
return (valeur, couleur)
v, c = tire_carte()
print(v, "de", c)
# affiche: 10 de trèfle
Ce qu’on a vu jusqu’ici :
• des types simples (entiers, booléens, . . . ) ;
• des structures de données (listes, dictionnaires, . . . ) ;
• des fonctions qui manipulent ces types ou ces structures de données ;
• ds fonctions qui s’appellent les unes les autres.
On appelle cet ensemble de concepts, cette façon d’écrire du code, un paradigme, et c’est un paradigme procédural.
On va passer à un autre paradigme : l’orienté objet.
57
Programmation en Python
Une meilleure définition, c’est de dire que la programmation orientée objet permet de mettre au même endroit :
• des données ;
• des fonctions qui opèrent sur ces données.
L’important c’est que les deux aillent ensemble !
Note : ce n’est pas la meilleure définition de l’orienté objet, mais on s’en contentera pour le moment. . .
class MaClasse:
# du code ici
Notez qu’on n’utilise pas le snake case pour les noms de classes, mais le Pascal Case (ou Camel Case) : Le nom
commence par une majuscule, et on alterne minuscules et majuscules pour séparer les mots.
Comme les fonctions, les classes contiennent un corps, qui est le bloc indenté en dessous du mot-clé class, de nom
de la classe et du : en fin de ligne.
Les classes sont utilisées pour construire des instances.
class MaClasse:
pass
Dans ce cas, on crée une instance en mettant le nom de la classe suivi d’une paire de parenthèses, un peu comme pour
appeler une fonction :
mon_instance = MaClasse()
Ici, mon_instance est une instance de la classe MaClasse. Notez que mon_instance utilise snake case, comme
toutes les variables qu’on a vues jusqu’ici.
10.6 Attributs
y = a.x
func = a.x
func(10)
Ici, on crée une variable func qui prend la valeur de l’attribut x dans l’instance a, puis on l’appelle avec l’argument
10 à la ligne suivante.
Le code suivant fait exactement la même chose, mais avec une ligne de moins :
a.x(10)
On peut créer des attributs dans n’importe quel instance, en utilisant l’assignation :
mon_instance = MaClasse()
class MaClasse:
def ma_méthode(self):
return 42
Notez que les méthodes sont aussi des attributs. Leur valeur est une fonction qui se comporte légèrement différemment
des fonctions qu’on a vu jusqu’ici.
10.6. Attributs 59
Programmation en Python
Une méthode ne peut être appelée que depuis une instance de la classe :
class MaClasse:
def ma_méthode(self):
return 42
ma_méthode()
# erreur: NameError
mon_instance = MaClasse()
résultat = mon_instance.ma_méthode()
print(résultat)
# affiche: 42
Notez qu’on ne passe pas d’argument quand on appelle ma_méthode depuis l’instance.
class MaClasse:
def affiche_attribut_x(self):
# Accès à l'attribut `x` dans `self`
print(self.x)
mon_instance = MaClasse()
mon_instance.x = 42
mon_instance.affiche_attribut_x()
# affiche: 42
class MaClasse:
def crée_attribut_x(self):
self.x = 42
def affiche_attribut_x(self):
print(self.x)
mon_instance = MaClasse()
mon_instance.affiche_attribut_x()
# erreur: `mon_instance` n'a pas d'attribut `x`
mon_instance.crée_attribut_x()
mon_instance.affiche_attribut_x()
# affiche: 42
Les méthodes peuvent aussi prendre plusieurs arguments, en plus de self, mais self doit toujours être le premier
argument.
class MaClasse
def crée_attribut_x(self, valeur_de_x):
self.x = valeur_de_x
def affiche_attribut_x(self);
print(self.x)
mon_instance = MaClasse()
mon_instance.crée_attribut_x(42)
mon_instance.affiche_attribut_x()
# affiche: 42
Comme les méthodes sont aussi des attributs, les méthodes d’une instance peuvent s’appeler les unes les autres :
class MaClasse:
def méthode_1(self):
print("démarrage de la méthode 1")
print("la méthode 1 affiche bonjour")
print("bonjour")
print("fin de la méthode 1")
def méthode_2(self):
print("la méthode 2 appelle la méthode 1")
self.méthode_1()
print("fin de la méthode 2")
mon_instance = MaClasse()
mon_instance.méthode_2()
Si vous définissez une méthode nommée __init__, celle-ci est appelée automatiquement quand l’instance est
construite.
On dit que c’est une méthode « magique » parce qu’elle fait quelque chose sans qu’on l’appelle explicitement.
On utilise souvent __init__ pour créer des attributs :
class MaClasse:
def __init__(self):
self.x = 1
self.y = 2
mon_instance = MaClasse()
On prend souvent les valeurs des attributs à créer en arguments de la méthode __init__ :
class MaClasse:
def __init__(self, x, y):
self.x = x
self.y = y
Dans ce cas, les arguments de la méthode __init__ apparaissent à l’intérieur des parenthèses après le nom de la classe
:
mon_instance = MaClasse(3, 4)
print(mon_instance.x)
# affiche: 3
print(mon_instance.y)
# affiche: 4
Note: Pour cette raison, __init__ est souvent appelé le constructeur de la classe.
Une bonne pratique consiste à créer tous les attributs nécessaires dans le constructeur.
Si les atttributs ont une valeur par défaut, ou une valeur initiale, on peut les créer dans le constructeur directement.
Sinon, on créera l’attribut avec une valeur qui viendra d’un paramètre du constructeur (souvent avec le même nom)
Premier example:
class Point2D:
# x et y n'ont pas de bonne valeur par défault,
(suite sur la page suivante)
origine = Point2D(0, 0)
Deuxième example:
class Joueur:
# Quand on crée un joueur, il a un score à 0
def __init__(self, nom):
self.nom = nom
self.score = 0
alice = Joueur("alice")
10.13 Récapitulatif
Ainsi, on peut ranger au même endroit des données et des fonctions opérant sur ces données.
Les données sont les attributs, et les fonctions opérant sur ces attributs sont les méthodes.
On peut ainsi séparer les responsabilités à l’intérieur d’un code en les répartissant entre plusieurs classes.
10.13. Récapitulatif 63
Programmation en Python
# Dans bonjour.py
a = 42
Comme un fichier = un module, on vient de créé un module bonjour contenant une variable a.
Si maintenant on crée un fichier salutations.py dans le même répertoire, on peut accéder à cette variable en
important le module avec le mot-clé import :
# Dans salutations.py
import bonjour
print(bonjour.a)
# affiche: 42
Note: Le nom du module est écrit directement, ce n’est pas une chaîne de caractères.
On voit que la variable a dans bonjour.py est devenue un attribut du module bonjour lorsqu’il a été importé.
65
Programmation en Python
# Dans bonjour.py
a = 42
def dire_bonjour():
print("Bonjour!")
# Dans salutations.py
import bonjour
bonjour.dire_bonjour()
# affiche: Bonjour!
# Dans bonjour.py
print("Je suis le module bonjour et tu viens de m’importer")
def dire_bonjour():
....
# Dans salutation.py
import bonjour
# affiche: Je suis le module bonjour et tu viens de m’importer
import bonjour
# affiche: rien
La bibliothèque standard est une collection de modules directement utilisables fournis à l’installation de Python.
Exemple: sys, random, . . .
Toute la bibliothèque standard est documentée, et la traduction en Français est en cours :
https://docs.python.org/fr/3/library/index.html
Mettez ce lien dans vos favoris, il vous sera très utile.
12.1 Couplage
12.1.1 Définition
12.1.2 Exemple
Ici on veut représenter des chats et des humains qui adoptent (ou non) des chats. Tous les chats ont un nom, et tous les
humains ont un prénom.
On peut utiliser pour cela deux classes : Chat et Humain.
class Chat:
def __init__(self, nom):
self.nom = nom
class Humain:
def __init__(self, prénom):
self.prénom = prénom
alice = Humain(prénom="Alice")
print(alice.prénom)
# affiche: Alice
Maintenant on veut que les humains puissent adopter des chats. Pour cela, on peut ajouter la méthode adopte() dans
la classe Humain.
69
Programmation en Python
class Humain:
def __init__(self, prénom):
self.prénom = prénom
On peut accéder au nom du chat depuis la méthode adopte(), en utilisant la syntaxe nom.attribut vue
précédemment :
class Humain:
def __init__(self, prénom):
self.prénom = prénom
12.1.3 Couplage
class Humain:
...
def adopte(self, chat):
print(self.prénom, "adopte", chat.nom)
Notez également que nous avons écrit chat.nom. ainsi, la méthode adopte() ne peut être appelée que par une instance
qui a un attribut nom, sinon on aura une erreur.
Donc si on modifie la classe Chat et qu’on renomme l’attribut nom en surnom par exemple, la méthode adopte() de
la classe Humain cessera de fonctionner : on dit qu’on a un couplage entre les classes Chat et Humain.
12.2 Composition
12.2.1 Définition
Exemple :
def calcule_x():
...
# du code ici
def fait_un_truc_avec_x(x):
...
# du code ici
x = calcule_x()
fait_un_truc_avec_x(x)
On voit que la fonction fait_un_truc_avec_x() prend un argument x, qui est retourné par la fonction calcule_x().
fait_un_truc_avec_x() doit donc être appelée après calcule_x(). On dit que fait_un_truc_avec_x() dépend
de calcule_x().
Un bon moyen d’introduire une dépendance entre deux classes est d’utiliser les constructeurs.
Revoyons la classe Chat :
class Chat:
def __init__(self, nom):
self.nom = nom
Comme le constructeur de la classe Chat prend un nom en argument, il est impossible de construire des chats sans nom
:
chat = Chat()
# erreur: TypeError: __init__() missing 1 required positional argument: 'nom'
De la même façon, si on veut que tous les enfants aient un chat (pourquoi pas, après tout), on peut avoir une classe
Enfant, dont le constructeur prend une instance de chat en plus du prénom :
class Enfant:
def __init__(self, prénom, chat):
self.prénom = prénom
self.chat = chat
alice = Enfant("Alice")
# erreur: TypeError: __init__() missing 1 required positional argument: 'chat'
12.2. Composition 71
Programmation en Python
Maintenant qu’on vit dans un monde où tous les enfants ont chacun un chat, on peut par exemple consoler tous les
enfants en leur demandant de caresser leur chat, chat qui va ronronner et faire plaisir à son propriétaire.
voici comment on peut coder cela : d’abord, on rajoute les méthodes caresse() et ronronne() dans la classe chat:
class Chat:
def __init__(self, nom):
self.nom = nom
def ronronne(self):
print(self.nom, 'fait: "prrrrr"')
def caresse(self):
self.ronronne()
class Enfant:
def __init__(self, prénom, chat):
self.prénom = prénom
self.chat = chat
def console(self):
self.chat.caresse()
Si on combine ces deux classes dans le même morceau de code et qu’on exécute les instructions suivantes :
Chapitre 13 - Bibliothèques
13.1 Introduction
Souvenez-vous, dans le chapitre 12, nous avons vu que le code suivant fonctionne s’il y a un ficher foo.py quelque
part qui contient la fonction bar:
import foo
foo.bar()
Ce fichier peut être présent soit dans le répertoire courant, soit dans la bibliothèque standard Python.
Vous connaissez peut-être le rôle de la variable d’environnement PATH. Celle-ci contient une liste de chemins, séparés
par le caractère : et est utilisée par votre shell pour trouver le chemin complet des commandes que vous lancez.
Par exemple :
PATH="/bin:/usr/bin:/usr/sbin"
$ ifconfig
# lance le binaire /usr/sbin/ifconfig
$ ls
# lance le binaire /bin/ls
Le chemin est « résolu » par le shell en parcourant la liste de tout les chemins de la variable PATH, et en regardant si
le chemin complet existe. La résolution s’arrête dès le premier chemin trouvé.
Par exemple, si vous avez PATH="/home/user/bin:/usr/bin" et un fichier ls dans /home/user/bin/ls, c’est
cette commande-là (et non /bin/ls) qui sera utilisé quand vous taperez ls.
73
Programmation en Python
13.2 sys.path
En Python, il existe une variable path prédéfinie dans le module sys qui fonctionne de manière similaire à la variable
d’environnement PATH.
Si j’essaye de l’afficher sur ma machine, voici ce que j’obtiens :
import sys
print(sys.path)
[
"",
"/usr/lib/python3.8",
"/usr/lib/python3.8/lib-dynload",
"/home/dmerej/.local/lib/python3.8/",
"/usr/lib/python3.8/site-packages",
]
Le résultat dépend :
• du système d’exploitation ;
• de la façon dont Python a été installé ;
• et de la présence ou non de certains répertoires.
En fait, sys.path est construit dynamiquement par l’interpréteur Python lors de son démarrage.
Notez également que sys.path commence par une chaîne vide. En pratique, cela signifie que le répertoire courant à
la priorité sur tout le reste.
Prenons un exemple. Si vous ouvrez un explorateur de fichiers dans le deuxième élément de la liste de sys.path
(/usr/lib/python3.8/ sur ma machine), vous trouverez un grand nombre de fichiers Python.
Notamment, vous devriez trouver un fichier random.py dans ce répertoire.
En fait, vous trouverez la plupart des modules de la bibliothèque standard dans ce répertoire.
Maintenant, imaginons que vous avez un deuxième fichier random.py dans votre répertoire courant. Finalement,
imaginez que vous lancez un fichier foo.py contentant import random dans ce même répertoire.
Et bien, c’est le fichier random.py de votre répertoire qui sera utilisé, et non celui de la bibliothèque standard !
Un autre aspect notable de sys.path est qu’il ne contient que deux répertoires dans lesquels l’utilisateur courant peut
potentiellement écrire : le chemin courant et le chemin dans ~/.local/lib. Tous les autres (/usr/lib/python3.
8/, etc.) sont des chemins « système » et ne peuvent être modifiés que par un compte administrateur (avec root ou
sudo, donc).
La situation est semblable sur macOS et Windows.
Prenons un exemple :
# dans foo.py
import tabulate
scores = [
["John", 345],
["Mary-Jane", 2],
["Bob", 543],
]
table = tabulate.tabulate(scores)
print(table)
$ python3 foo.py
--------- ---
John 345
Mary-Jane 2
Bob 543
--------- ---
Ici, le module tabulate n’est ni dans la bibliothèque standard, ni écrit par l’auteur du script foo.py. On dit que c’est
une bibliothèque tierce.
On peut trouver le code source de tabulate facilement. La question qui se pose alors est : Comment faire en sorte que
sys.path contienne le module tabulate ?
Eh bien, plusieurs solutions s’offrent à vous.
Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets :
Comme vous lancez votre gestionnaire de paquets avec sudo, celui-ci sera capable d’écrire dans les chemins système
de sys.path.
13.3.2 À la main
Une autre méthode consiste à partir des sources : Par exemple, si le paquet de votre distribution n’est pas assez récent,
ou si vous avez besoin de modifier le code de la bibliothèque en question.
Voici une marche à suivre possible :
1. Récupérer les sources de la version qui vous intéresse dans la section téléchargement de bitbucket ;
2. Extraire l’archive, par exemple dans src/tabulate ;
3. Se rendre dans src/tabulate et lancer python3 setup.py install --user.
La plupart des bibliothèques Python contiennent un setup.py à la racine de leurs sources. Il sert à plein de choses, la
commande install n’étant qu’une parmi d’autres.
Le fichier setup.py contient en général simplement un import de setuptools, et un appel à la fonction setup(),
avec de nombreux arguments :
# tabulate/setup.py
from setuptools import setup
setup(
name='tabulate',
version='0.8.1',
description='Pretty-print tabular data',
py_modules=["tabulate"],
scripts=["bin/tabulate"],
...
)
Par défaut, setup.py essaiera d’écrire dans un des chemins système de sys.path, d’où l’utilisation de l’option
--user.
Voici à quoi ressemble la sortie de la commande :
$ cd src/tabulate
$ python3 setup.py install --user
running install
...
Copying tabulate-0.8.4-py3.7.egg to /home/dmerej/.local/lib/python3.7/site-packages
...
Installing tabulate script to /home/dmerej/.local/bin
Notez que le module a été copié dans ~/.local/lib/python3.7/site-packages/ et le script dans ~/.local/bin.
Cela signifie que tous les scripts Python lancés par l’utilisateur courant auront accès au module tabulate.
Notez également qu’un script a été installé dans ~/.local/bin. Une bibliothèque Python peut contenir aussi bien des
modules que des scripts.
Un point important est que vous n’avez en général pas besoin de lancer le script directement. Vous pouvez utiliser
python3 -m tabulate. Procéder de cette façon est intéressant puisque vous n’avez pas à vous soucier de rajouter le
chemin d’installation des scripts dans la variable d’environnement PATH.
13.4 Dépendances
import cli_ui
headers=["name", "score"]
data = [
[(bold, "John"), (green, 10.0)],
[(bold, "Jane"), (green, 5.0)],
]
cli_ui.info_table(data, headers=headers)
Pour ce faire, elle repose sur la bibliothèque tabulate vue précédemment. On dit que cli-ui dépend de tabulate.
La déclaration de la dépendance de cli-ui vers tabulate s’effectue également dans le fichier setup.py :
setup(
name="cli-ui",
version="0.9.1",
install_requires=[
"tabulate",
...
],
...
)
13.4.2 pypi.org
On comprend dès lors qu’il doit nécessairement exister un annuaire permettant de relier les noms de dépendances à
leur code source.
Cet annuaire, c’est le site pypi.org. Vous y trouverez les pages correspondant à tabulate et cli-ui.
13.4.3 pip
pip est un outil qui est fréquemment installé en même temps que l’interpréteur Python. Vous pouvez également
l’installer séparément grâce au script get-pip.py, en lançant python3 get-pip.py --user.
Il est conseillé de toujours lancer pip avec python3 -m pip. De cette façon, vous êtes certains d’utiliser le module
pip correspondant à votre binaire python3, et vous ne dépendez pas de ce qu’il y a dans votre PATH.
pip est capable d’interroger le site pypi.org pour retrouver les dépendances, et également de lancer les différents
scripts setup.py.
13.4. Dépendances 77
Programmation en Python
Comme de nombreux outils, il s’utilise à l’aide de commandes. Voici comment installer cli-ui à l’aide de la
commande “install” de pip :
On y retrouve les bibliothèques cli-ui et tabulate, bien sûr, mais aussi la bibliothèque gaupol, qui correspond au
programme d’édition de sous-titres que j’ai installé à l’aide du gestionnaire de paquets de ma distribution. Précisons
que les modules de la bibliothèque standard et ceux utilisés directement par pip sont omis de la liste.
On constate également que chaque bibliothèque possède un numéro de version.
Les numéros de version remplissent plusieurs rôles, mais l’un des principaux est de spécifier des changements
incompatibles.
Par exemple, pour cli-ui, la façon d’appeler la fonction ask_choice a changé entre les versions 0.7 et 0.8, comme
le montre cet extrait du changelog :
The list of choices used by ask_choice is now a named keyword argument:
Plusieurs possibilités :
• On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela n’est pas toujours possible ni souhaitable.
• Une autre solution est de spécifier des contraintes sur le numéro de version dans la déclaration des dépendances.
Par exemple :
setup(
install_requires=[
"cli-ui < 0.8",
...
]
)
Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets.
Les mainteneurs de votre distribution font en sorte qu’ils fonctionnent bien les uns avec les autres. Par exemple, le
paquet python3-cli-ui ne sera mis à jour que lorsque tous les paquets qui en dépendent seront prêts à utiliser la
nouvelle API.
En revanche, si vous lancez sudo pip (où pip avec un compte root), vous allez écrire dans ces mêmes répertoire et
vous risquez de « casser » certains programmes de votre système.
Mais il y a un autre problème encore pire. . .
Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent tous les deux de cli-ui, mais l’un des
deux utilise cli-ui 0.7 et l’autre cli-ui 0.9. Que faire ?
La solution est d’utiliser un environnement virtuel (virtualenv en abrégé). C’est un répertoire isolé du reste du système.
La commande python3 -m venv fonctionne en général partout, dès l’installation de Python3 (out of the box, en Anglais),
sauf sur Debian et ses dérivées.
Si vous utilisez Debian, la commande pourrait ne pas fonctionner. En fonction des messages d’erreur que vous obtenez,
il est possible de résoudre le problème en :
• installant le paquet python3-venv ;
• ou en utilisant d’abord pip pour installer virtualenv, avec python3 -m pip install virtualenv
--user puis en lançant python3 -m virtualenv foo-venv.
À noter :
• Le répertoire « global » dans ~/.local/lib a disparu.
• Seuls quelques répertoires systèmes sont présents (ils correspondent plus ou moins à l’emplacement des modules
de la bibliothèque standard).
• Un répertoire au sein du virtualenv a été ajouté.
Ainsi, l’isolation du virtualenv est reflété dans la différence de la valeur de sys.path.
Il faut aussi préciser que le virtualenv n’est pas complètement isolé du reste du système. En particulier, il dépend encore
du binaire Python utilisé pour le créer.
Par exemple, si vous utilisez /usr/local/bin/python3.7 -m venv foo-37, le virtualenv dans foo-37 utilisera
Python 3.7 et fonctionnera tant que le binaire /usr/local/bin/python3.7 existe.
Cela signifie également qu’il est possible qu’en mettant à jour le paquet python3 sur votre distribution, vous rendiez
inutilisables les virtualenvs créés avec l’ancienne version du paquet.
D’après ce qui précède, le virtualenv ne devrait contenir aucun module en dehors de la bibliothèque standard et de pip
lui-même.
On peut s’en assurer en lançant python3 -m pip freeze depuis le virtualenv et en vérifiant que rien ne s’affiche :
On peut alors utiliser le module pip du virtualenv pour installer des bibliothèques dans celui-ci :
Cette fois, aucune bibliothèque n’est marquée comme déjà installée, et on récupère donc cli-ui et toutes ses
dépendances.
On a enfin notre solution pour résoudre notre conflit de dépendances : On peut simplement créer un virtualenv par
projet. Ceci nous permettra d’avoir effectivement deux versions différentes de cli-ui, isolées les unes des autres.
Devoir préciser le chemin du virtualenv en entier pour chaque commande peut devenir fastidieux ; heureusement, il est
possible d’activer un virtualenv, en lançant une des commandes suivantes :
• source foo-venv/bin/activate, si vous utilisez un shell POSIX ;
• source foo-venv/bin/activate.fish, si vous utilisez Fish ;
• foo-venv\bin\activate.bat, sous Windows.
Une fois le virtualenv activé, taper python, python3 ou pip utilisera les binaires correspondants dans le virtualenv
automatiquement, et ce, tant que la session du shell sera ouverte.
Le script d’activation ne fait en réalité pas grand-chose à part modifier la variable PATH et rajouter le nom du virtualenv
au début de l’invite de commandes :
# Avant
user@host:~/src $ source foo-env/bin/activate
(suite sur la page suivante)
Le système de gestions des dépendances de Python peut paraître compliqué et bizarre, surtout venant d’autres langages.
Mon conseil est de toujours suivre ces deux règles :
• un virtualenv par projet ;
• toujours utiliser pip depuis un virtualenv.
Certes, cela peut paraître fastidieux, mais c’est une méthode qui vous évitera probablement de vous arracher les cheveux
(croyez-en mon expérience).
Voici un exemple d’arborescence qui montre deux projets, projet-1 et projet-2, chacun avec son virtualenv dédié :
projets
projet-1
projet_1.py
.venv
bin
include
lib
...
projet-2
projet_2.py
.venv
bin
include
lib
...
Notez bien que les sources de chaque projet projet_1.py et projet_2.py sont au même niveau que le répertoire
.venv et non à l’intérieur de celui-ci.
Vous pouvez aussi retenir la règle suivante : étant donné un répertoire « X » contenant les sources d’un projet, les
commandes à lancer pour créer le virtualenv seront :
$ cd X/
$ python3 -m venv .venv
On vous a peut-être déjà dit que l’informatique consiste à manipuler des suites de 0 et et de 1, mais qu’en est-il
exactement ?
De manière surprenante, la réponse à cette question va nous emmener sur un chemin qui va parler de maths, puis de
comment le langage Python peut interagir avec « l’extérieur ».
Si je vous parle de ce que représente le texte 342 vous pouvez le voir de deux façons :
1. C’est une suite de chiffres: 3, puis 4, puis 2 ;
2. C’est un nombre (quelque part entre 300 et 350).
On se sert des chiffres de 0 à 9 pour représenter des nombres.
14.1.2 La base 10
Plus exactement, pour passer de la suite de chiffres 342 au nombre, on part de la fin, puis on ajoute chaque chiffre,
multiplié par la puissance de 10 adéquate :
2 * 1
+ 4 * 10
+ 2 * 10 * 10
soit :
85
Programmation en Python
2
+ 40
+ 300
14.1.3 La base 16
En informatique, on se sert souvent de la base 16. C’est le même principe : on se sert des « chiffres » de 0 à F (A vaut
10, B vaut 11, jusqu’à F qui vaut 15).
Ainsi, la suite DA2 peut être interprétée comme suit :
2 * 1
+ 10 * 16
+ 13 * 16 * 16
soit :
2
+ 160
+ 3328
soit 3746.
On appelle aussi la base 16 la base hexadécimale, ou hexa en abrégé.
14.1.4 La base 2
0 * 1
+ 1 * 2
+ 1 * 2 * 2
soit :
0
+ 2
+ 4
soit 6.
14.1.6 À retenir
Ces paquets de 8 ne veulent rien dire en eux-mêmes. Ils n’ont de sens que dans le cadre d’une convention.
Par exemple, l’octet “10100100” peut être un nombre écrit en binaire (164 en l’occurrence), mais peut avoir une toute
autre signification.
Le nombre de valeurs possible augmente très rapidement avec le nombre d’octets :
• 1 octet, c’est 255 valeurs possibles (2 ** 8) ;
• 2 octets, c’est 65.536 valeurs possibles (2 ** 16) ;
• 4 octets, c’est 4.294.967.296 valeurs possibles (2 ** 32).
On se sert des préfixes 0b et 0x pour noter les nombres en base binaire ou hexadécimale respectivement :
print(0b1110)
# affiche: 6
print(0xDA2)
# affiche: 3490
x = 0b0010010 # 18
y = 0b0010011 # 19
z = 0b1010010 # 82
Notez que le premier bit est plus « fort » que le dernier. On passe de 18 à 82 quand le premier bit passe de 0 à 1. À
l’inverse, on passe seulement de 18 à 19 quand le dernier bit passe de 0 à 1. On dit qu’on a utilisé un mode Petit-boutisme
(« little endian » en Anglais).
Voir aussi:
La page wikipedia consacée au boutisme.
On peut construire des listes d’octets en utilisant bytearray et une liste de nombres :
data = bytearray(
[0b1100001,
0b1100010,
0b1100011
]
)
# equivalent:
data = bytearray([97, 98, 99])
# equivalent aussi:
data = bytearray([0x61, 0x62, 0x63]
14.1.10 Texte
On peut aussi interpréter les octets comme du texte : C’est la table ASCII.
La table se lit ainsi : si on veut connaître la suite de 0 et de 1 qui correspond à B, on lit les 3 premiers bits de haut en
bas sur la colonne 100, puis les 4 bits sur la ligne 0010. Du coup “B” s’écrit en 7 bits 1000010, soit “66” en décimal,
et “42” en hexadécimal.
x = chr(0x42)
print(x)
# affiche: B
x = ord('B')
print(x)
# affiche: 66
Python utilise ASCII pour afficher les bytearrays si les caractères sont « imprimables » :
Note: Notez que Python rajoute quelque chose qui ressemble à un appel de fonction lorsqu’il affiche le bytearray : ce
n’est pas un vrai appel de fonction.
Notez bien que ce qu’affiche Python n’est qu’une interpétation d’une séquence d’octets.
14.1.14 Types
La variable b"abc" est une « chaîne d’octets », de même que "abc" est une « chaîne de caractères ».
Python appelle ces types bytes et str :
print(type("abc"))
# affiche: str
print(type(b"abc"))
# affiche: bytes
De la même manière qu’on ne peut pas modifier un caractère à l’intérieur d’une string, on ne peut pas modifier un bit,
ou un octet dans une variable de type bytes :
a = "foo"
# a[0] = "f" => TypeError: 'str' object does not support item assignment
b = b"foo"
# b[0] = 1 => TypeError: 'bytes' object does not support item assignment
b = bytearray(b"foo")
b[0] = 103
print(b)
# affiche: bytearray(b"goo")
text = "chaise"
encodé = text.encode("ascii")
print(encodé)
# affiche: b"chaise"
bytes = b"table"
décodé = bytes.decode("ascii")
print(décodé)
# affiche: b"table"
Notez que dans le deuxième exemple, on est bien en train de « décoder » un paquet de 0 et de 1. Il peut s’écrire ainsi :
bytes = b"\x74\x61\x62\x6c\x65"
décodé = bytes.decode("ascii")
print(décodé)
# affiche: table
Vous avez sûrement remarquer qu’il n’y a pas de caractères accentués dans ASCII. Du coup, il existe d’autres
conventions qu’on appelle « encodage ».
On peut spécifier l’encodage quand on appelle la méthode decode():
Notez que la même suite d’octets a donné des résultats différents en fonction de l’encodage !
14.1.18 Unicode
encodé = "abc".encode("utf-8")
print(encodé)
# affiche: b'abc'
encodé = "café".encode("utf-8")
print(encodé)
# affiche: b'caf\xc3\xa9"
Enfin, certains caractères (comme les emojis) sont représentés par 3 voire plus octets.
Avertissement: Toutes les séquences d’octets ne sont pas forcément valides quand on veut les décoder en UTF-8.
14.1.20 Conséquences
• On peut représenter tout type de texte avec UTF-8 (latin, chinois, coréen, langues disparues, . . . ).
• On ne peut pas accéder à la n-ième lettre directement dans une chaîne encodée en UTF-8, il faut parcourir lettre
par lettre (ce qui en pratique est rarement un problème).
14.2 Fichiers
Cela vous est peut-être déjà arrivé : Imaginons que vous ayez dans votre répertoire courant un code source python dans
mon_script.py et un pdf dans cours.pdf.
Vous pourrez ouvrir mon_script.py dans un éditeur de texte, mais pas cours.pdf, ou alors ça affichera n’importe
quoi.
En Python, on utilise la fonction native open(), en passant en argument le chemin du fichier.
Selon que l’on veuille accéder au texte dans le fichier ou aux données binaires qui sont à l’intérieur, on utilisera
l’argument "r" ou "rb" (“r” comme “read”, et “b” comme “binary”)
Enfin, open() renvoie un « file-objet », qu’on note souvent “f”, qui contient une méthode read() pour lire le contenu
du fichier.
En pratique, voici ce que cela donne :
f = open("mon_script.py", "r")
code = f.read()
print(code)
# affiche le code présent dans le fichier mon_script.py
f = open("cours.pdf", "rb")
données = f.read()
# données est mainteant une grosse suite
# d'octets
f = open("cours.pdf", "r")
f.read()
# Erreur: UnicodeDecodeError: 'utf-8' codec can't
# decode byte 0xd0 in position 10
Comme on doit utilisé l’option rb pour lire le pdf, on dit parfois que le fichier pdf est un fichier « binaire », par
opposition avec mon_script.py qui est un fichier « texte ».
Ça n’a pas vraiment de sens : les deux fichiers sont stockés sur votre ordinateur comme des suites d’octets,
indépendamment de leur contenu.
Il se trouve que l’un des deux contient une suite d’octets qui est décodable en tant que string. En fait, sous le capot, la
suite d’octets renvoyée dans le premier example a été décodée avec l’encodage par défaut de votre système. On peut
d’ailleurs passer l’encodage en argument à open() :
On peut aussi écrire dans un fichier, toujours avec open(), mais cette fois-ci avec la méthode write() du file-objet.
On peut écrire du texte avec l’option "w" et une chaîne de caractères :
f = open("mon_script.py", "w")
f.write("Nouveau contenu!")
Et écrire directement des données binaires avec "wb" et une suite d’octets
f = open("cours.pdf", "wb")
f.write(b"\x0c\x1f...")
Encore une fois, sous le capot, la chaîne de caractères sera encodée par Python avant d’être écrite dans le fichier texte.
Notez qu’il est impératif de fermer les fichiers que vous ouvrez, que ce soit en lecture ou en écriture, en appelant la
méthode close() :
f = open("mon_poéme.py", "w")
f.write(premier_vers)
f.write(deuxième_vers)
f.close()
14.2.4 Conseils
14.2. Fichiers 93
Programmation en Python
15.1 Démarrage
Jusqu’ici, on a toujours lancé la commande Python avec python suivi du nom d’un fichier source.
Il est égalemnet possible de lancer la commande python3 sans argument.
Dans ce cas, on se retrouve avec une autre invite de commandes :
$ python3
Python 3.7.1 (default, Oct 22 2018, 10:41:28)
[GCC 8.2.1 20180831] on linux
Type "help", "credits" or "license" for more information.
>>>
Notez les trois chevrons: >>>. Cela vous permet de différencier l’invite de commandes du système d’exploitation de
celle de Python.
• Système d’exploitation -> Python : taper python3 (sans arguments).
• Python -> Système d’exploitation : taper quit().
95
Programmation en Python
>>> a = 42
>>> a
42
>>> b = 4
>>> a + b
46
# dans bonjour.py
salutation = "Bonjour,"
def dire_bonjour(nom):
print(salutation, nom)
On peut démarrer un interpréteur interactif dans le répertoire contenant le fichier bonjour`.py, et « jouer » avec les
attributs du module bonjour :
Avertissement: Si le contenu de bonjour.py change, il faut relancer l’interpréteur interactif et refaire l’import.
Chapitre 16 - Héritage
Dans un chapitre précédent, on a parlé de composition qui décrit une classe à l’intérieur d’une autre classe.
Pour rappel :
class Chat:
def __init__(self, nom):
self.nom = nom
def ronronne(self):
print(self.nom, 'fait: "prrrrr"')
def caresse(self):
self.ronronne()
class Enfant:
def __init__(self, prénom, chat):
self.chat = chat
def console(self):
self.chat.caresse()
99
Programmation en Python
16.2 Vocabulaire
Ici on va parler d’héritage, qui décrit une autre relation entre classes, appelée parfois un peu abusivement « partage de
code ».
Pour indiquer qu’une classe B hérite d’une classe A, on écrit A dans des parenthèses au moment de déclarer la classe B :
class A:
...
class B(A):
...
16.3 Utilisation
Si une méthode n’est pas trouvée dans la classe courante, Python ira la chercher dans la classe parente :
class A:
def méthode_dans_a(self):
print("dans A")
class B(A):
def méthode_dans_b(self):
print("dans B")
b = B()
b.méthode_dans_b()
# Affiche: 'dans B', comme d'habitude
b.méthode_dans_a()
# Affiche: 'dans A'
S’il y a plusieurs classes parentes, Python les remonte toutes une à une. On dit aussi qu’il y a une hiérarchie de classes
:
class A:
def méthode_dans_a(self):
print("dans A")
class B(A):
(suite sur la page suivante)
class C(B):
def méthode_dans_c(self):
print("dans C")
c = C()
c.méthode_dans_a()
# affiche: 'dans A'
class A:
def __init__(self):
print("initialisation de A")
class B(A):
...
b = B()
# affiche: "initialisation de A"
16.6 Attributs
class A:
def __init__(self):
self.attribut_de_a = 42
class B(A):
...
b = B()
print(b.attribut_de_a)
# affiche: 42
16.7 Surcharge
class A:
def une_méthode(self):
print("je viens de la classe A")
class B(A):
def une_méthode(self):
print("je viens de la classe B")
b = B()
b.une_méthode()
# affiche: "je viens de la classe B'
16.8 super()
On peut utiliser super() pour chercher explicitement une méthode dans la classe parente :
class A:
def une_méthode(self):
print("je viens de la classe A")
class B(A):
def une_méthode(self):
super().une_méthode()
print("je viens de la classe B")
b = B()
b.une_méthode()
# affiche:
# je viens de la classe A
# je viens de la classe B
class A:
def __init__(self):
self.attribut_de_a = "bonjour"
class B(A):
def __init__(self):
self.attribut_de_b = 42
b = B()
(suite sur la page suivante)
class A:
def __init__(self):
self.attribut_de_a = "bonjour"
class B(A):
def __init__(self):
super().__init__()
self.attribut_de_b = 42
b = B()
print(b.attribut_de_b)
# affiche: 42
print(b.attribut_de_a)
# affiche: "bonjour"
Chapitre 17 - Décorateurs
17.1.1 Introduction
# Définition de la fonction:
def dire_bonjour(nom):
print("Bonjour " + nom)
# Appel
dire_bonjour("Max")
105
Programmation en Python
Il se trouve qu’en Python, on peut assigner des fonctions à des variables. C’est différent d’assigner le résultat de l’appel
à une fonction à une variable, et ça permet de retarder l’appel :
De façon cruciale, notez que l’on n’a pas mis de parenthèses à droite lorsqu’on a créé la variable
ma_fonction_qui_dit_bonjour.
On peut donc dire que lorsqu’on définit une fonction avec def ma_fonction() et un corps, il y a en réalité deux
étapes :
1. Python stocke le corps de la fonction quelque part ;
2. Il assigne le corps de celle-ci à une variable dont le nom est ma_fonction.
En Python, il est assez fréquent d’utiliser un code tel que celui-ci, souvent avec un dictionnaire :
fonctions_connues = {
"français": dire_bonjour_en_français,
"anglais": dire_bonjour_en_anglais,
}
if langue_parlée in fonctions_connues:
fonction = fonctions_connues[langue_parlée]
fonction(prénom)
def appelle_deux_fois(f):
f()
f()
def crier():
print("Aline !")
appelle_deux_fois(crier)
# affiche:
# Aline !
# Aline !
def affiche_message(message):
def affiche():
print(message)
affiche()
affiche_message("Bonjour")
# affiche: Bonjour
def affiche_message(message):
def affiche():
print(message)
affiche()
# NameError: 'affiche' is not defined
En réalité, on combine souvent les closures avec des fonctions qui retournent d’autres fonctions :
def fabrique_fonction_qui_additionne(n):
def fonction_résultat(x):
return x + n
return fonction_résultat
additionne_2 = fabrique_fonction_qui_additionne(2)
y = additionne_2(5)
print(y)
# affiche: 7
Le fait qu’on puisse traiter les fonctions comme n’importe quelle autre valeur (c’est-à-dire les assigner à des variables,
les passer en argument et les retourner), est caractéristique des langages dits « fonctionnels ». Python est donc à la fois
un langage impératif, objet et fonctionnel. On dit que c’est un langage multi-paradigmes.
17.2 Décorateurs
17.2.1 Définition
def mon_décorateur(fonction):
def fonction_retournée():
# fait quelque chose avec l'argument `fonction`, par exemple
# l'appeler avec un argument:
fonction(42)
return fonction_retournée
@mon_décorateur
def ma_fonction_décorée(un_argument):
fais_un_truc_avec(un_argument)
def ma_fonction_décorée(un_argument):
fais_un_truc_avec(un_argument)
ma_fonction_décorée = mon_décorateur(ma_fonction_décorée)
On peut faire un décorateur qui nous empêche d’appeler une fonction sous certaines conditions :
def pas_pendant_la_nuit(fonction):
def résultat():
if il_fait_nuit:
print("chut")
else:
fonction()
return résultat
@pas_pendant_la_nuit
def dire_bonjour():
print("bonjour")
il_fait_nuit = True
dire_bonjour()
# affiche: "chut"
On peut aussi avoir des arguments passés aux décorateurs. Dans ce cas, on a besoin de trois fonctions imbriquées. En
effet, il nous faut une fonction pour traiter l’argument message et une autre pour traiter l’argument fonction :
def affiche_message_avant_appel(message):
def décorateur(fonction):
def résultat():
print(message)
fonction()
return résultat
return décorateur
dire_bonjour()
# affiche:
# dire_bonjour est appelée
# bonjour
Chapitre 18 - Exceptions
On a souvent montré des exemples de code qui provoquaient des erreurs au cours des chapitres précédents. Il est temps
maintenant de se pencher davantage sur celles-ci.
Reprenons un exemple de code qui provoque une erreur, par exemple en essayant de diviser par zéro :
def mauvaise_fonction():
return 1 / 0
def fonction_intermédiaire():
mauvaise_fonction()
def fonction_principale():
fonction_intermédiaire()
fonction_principale()
111
Programmation en Python
Ceci est une pile d’appels. Elle permet de voir exactement par quelles fonctions on est passé et dans quel ordre. Elle
se lit de haut en bas :
• on a appelé fonction_principale() ;
• cette fonction a à son tour appelé fonction_intermédiaire() ;
• fonction_intermédiaire() a appelé mauvaise_fonction() ;
• mauvaise_fonction() a levé une exception.
Notez que chaque élément de la pile comprend :
• le nom de la fonction ;
• le chemin du module la contenant ;
• le numéro et la ligne précise du code qui a été appelé .
Il est important de bien lire les piles d’appels quand on cherche à comprendre d’où vient une exception.
Après la pile d’appels, on a le nom de l’exception et sa description.
Les exceptions sont toujours des instances de classes, et les classes d’exceptions héritent toujours de la classe
BaseException.
Le nom de l’exception est en réalité le nom de la classe, ici l’exception levée par la ligne return 1 / 0 est une instance
de la classe ZeroDivisionError.
Cette exception fait partie des nombreuses exceptions prédéfinies en Python. Ensemble, elles forment une hiérarchie
d’exceptions dont voici un extrait :
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- Exception
+-- ArithmeticError
| +-- ZeroDivisionError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- OSError
| +-- FileNotFoundError
+-- TypeError
+-- ValueError
IndexError est levée quand on essaye d’accéder à un index trop grand dans une liste :
ma_liste = ["pomme"]
ma_liste[2] = "abricot"
KeyError est levée quand on essaye d’accéder à une clé qui n’existe pas dans un dictionnaire :
scores = { "Alice" : 10 }
score_de_bob = scores["Bob"]
# KeyError: 'Bob'
18.2.2 ValueError
ValueError est levée (entre autres) quand on tente une mauvaise conversion :
18.2.3 KeyboardInterrupt
18.2.4 FileNotFoundError
FileNotFoundError est levée quand on essaye d’ouvrir, en lecture, un fichier qui n’existe pas :
On peut gérer (ou attraper) une exception en utilisant un bloc try/except et le nom d’une classe d’exception :
try:
a = 1 / 0
except ZeroDivisionError:
print("Quelqu'un a essayé de diviser par zéro!")
À noter : Le bloc dans try s’interrompt dès qu’une exception est levée, et on ne passe dans le bloc except que si une
exception est effectivement levée.
x = 14
y = 0
try:
z = x / y
print("z vaut", z)
except ZeroDivisionError:
print("Quelqu'un a essayé de diviser par zéro!")
x = 14
y = 2
try:
z = x / y
print("z vaut", z)
except ZeroDivisionError:
print("Quelqu'un a essayé de diviser par zéro!")
Notez que la ligne print("Quelqu'un a essayé de diviser par zéro!") n’a pas été exécutée.
Le mot après except doit être celui d’une classe, et l’exception n’est gérée que si sa classe est égale ou une fille de
celle-ci.
Par exemple, ceci fonctionne car ZeroDivisionError est bien une fille de la classe ArithmeticError :
x = 14
y = 0
try:
z = x / y
print("z vaut", z)
except ArithmeticError:
print("Quelqu'un a essayé une opération impossible")
try:
tente_un_truc_risqué()
except ZeroDivisionError:
print("raté : division par zéro!")
except FileNotFoundError:
print("raté : fichier non trouvé")
try:
tente_un_truc_risqué()
except (ZeroDivisionError, FileNotFoundError)
print("raté!")
try:
ouvrir_fichier()
except FileNotFoundError as e:
print("le fichier: ", e.filename, "n'existe pas")
Ici on utilise l’attribut filename de la classe FileNotFoundError pour afficher un message d’erreur.
18.4.1 raise
On peut lever explicitement une exception en appelant le mot-clé raise suivi d’une instance d’une classe.
Par exemple en utilisant une exception native :
def dire_bonjour(prénom):
if not prénom:
raise ValueError("prénom vide")
class OpérationImpossible(Exception):
pass
def ma_fonction():
if cas_impossible:
raise OpérationImpossible()
try:
tente_un_truc_risqué()
except ArithmeticError:
...
raise
18.5.1 else
Si on rajoute un bloc else après le except, le bloc n’est éxécuté que si aucune exception n’a été levée :
try:
tente_un_truc_risqué()
except (ZeroDivisionError, FileNotFoundError):
print("raté")
else:
print("ouf, ça a marché")
18.5.2 finally
Si on rajoute un bloc finally après le except, le bloc est exécuté dans tous les cas, qu’une exception ait été levé ou
non. On s’en sert souvent pour « annuler » du code qui aurait été utilisé dans le bloc try :
personnage = Personnage()
try:
personnage.entre_en_scène()
personnage.tente_un_truc_risqué()
except ZeroDivisionError:
print("raté")
finally:
personnage.quitte_la_scène()
Si dans le bloc try une exception différente de ZeroDivisionError est levée, on passera quand même dans le bloc
finally, puis l’exception sera levée à nouveau.
TODO: exemple plus complet
Chapitre 19 - Objets
On a déjà parlé de programmation orientée objet en Python, mais pour l’instant, on a vu que des classes et des instances.
Or, le concept d’objet existe bel et bien en Python, et il est plus que temps de vous le présenter.
Mais avant, il faut faire un petit détour par les attributs et méthodes de classes.
19.1.1 Rappels
class MaClasse
def ma_méthode(self):
print(self.mon_attribut)
Le bloc indenté en-dessous du mot-clé class s’appelle le corps de la classe. Et les méthodes sont définies avec le
mot-clé def dans le corps de la classe.
On dit que ce sont des méthodes d’instance parce qu’il faut créer une instance pour pouvoir les appeler :
mon_instance = MaClasse()
mon_instance.ma_méthode()
117
Programmation en Python
class MaClasse:
mon_attribut_de_classe = 42
Ici mon_attribut_de_classe existe à la fois dans les instances de MaClasse et dans la classe elle-même :
print(MaClasse.mon_attribut_de_classe)
# affiche: 42
mon_instance = MaClasse()
print(mon_instance.mon_attribut_de_classe)
# affiche: 42
Un point important est que les attributs de classe sont partagés entre toutes les instances. Voici un exemple d’utilisation
possible :
class Voiture:
nombre_total_de_voitures_fabriquées = 0
Notez que pour changer l’attribut de classe depuis une méthode, (comme dans la méthode __init__ ci-dessus) on
utilise le nom de la classe directement, et non pas self.
class Voiture:
nombre_total_de_voitures_fabriquées = 0
@classmethod
def fabrique_ferrari(cls):
return cls("ferrari", "rouge")
ferrari = Voiture.fabrique_ferrari()
Détaillons ce qu’il se passe sur la dernière ligne : à gauche du signe égal, il y a une variable, et à droite une expression
(Voiture.fabrique_ferrari())
L’expression est constitué d’une classe à gauche du point (Voiture), et d’un attribut à droite du point
fabrique_ferrari, suivi de parenthèses.
Comme fabrique_ferrari est une méthode de classe, on va appeler la méthode de classe fabrique_ferrari en
lui passant la classe courante en argument.
On arrive ainsi dans le corps de la méthode de classe fabrique_ferrari, et cls vaut la classe Voiture.
Finalement, on évalue l’expression cls("ferrari", rouge") en remplaçant cls par sa valeur, ce qui donne
Voiture("ferrari", "rouge") qui correspond bien à ce qu’on obtient : Une instance de la classe Voiture.
19.2 Objets
En fait, tout ce qu’on manipule en Python est un objet. Et tous les objets sont toujours des instances d’une classe. On
peut accéder à la classe qui a servi à instancier un objet avec la fonction type, par exemple:
class MaClasse:
pass
mon_instance = MaClasse()
print(type(mon_instance))
# affiche:
# <class '__main__.MaClasse'>
Mais aussi :
print(type(2))
# affiche: int
print(type("bonjour"))
# affiche: str
Donc en Python, les entiers sont des instances de la classe int, et les strings des instances de la classe str.
Ainsi, vous pouvez voir l’expression x = str(y) de deux façons :
• Soit on appelle la fonction native str pour convertir y en string.
• Soit on crée une nouvelle instance de la classe str en appelant le constructeur de la classe str avec y en argument.
Notez que ça ne s’arrète pas là :
def ma_fonction():
pass
(suite sur la page suivante)
print(type(ma_fonction))
# affiche: function
class MaClasse:
def ma_méthode(self):
pass
mon_instance = MaClasse()
print(type(mon_instance.ma_méthode))
# affiche: method
import sys
print(type(sys))
# affiche: module
Et même :
print(type(MaClasse))
# affiche: type
print(type(type))
# affiche: type
Et oui, les classes elles-mêmes sont des instances de classe ! (de la classe type)
Du coup en Python, le terme “objet” désigne toujours une instance de classe, même None est une instance d’une classe
(elle s’appelle NoneType).
Il est temps de revenir sur l’évaluation des expressions impliquant des attributs.
On a vu trois systèmes différents :
Appeler une fonction définie dans un module :
import mon_module
mon_module.ma_fonction()
mon_instance = MaClasse()
mon_instance.ma_méthode()
MaClasse.ma_méthod_de_classe()
D’après ce qu’on a vu dans la section précédente, on sait maintenant que dans tous les cas, à gauche du point se situe
un objet, et que tous les objets sont des instances d’une classe (appelé le « type » de l’objet).
Pour évaluer l’expression mon_objet.mon_attribut, où mon_objet est une instance de mon_type, Python cherche
toujours l’attribut dans deux endroits :
class A:
def f1(self):
print("f1 dans A")
def f2(self):
print("f2")
class B(A):
@classmethod
def f3(cls):
print("f3")
def f1(self):
print("f1 dans B")
b = B()
b.f1()
b.f3()
b.f2()
# affiche:
# f1 dans B
# f3
# f2
19.2.2 Conclusion
Maintenant, vous devriez comprendre pourquoi on dit parfois qu’en Python, tout est objet.
Dans un prochain chapitre, on expliquera pourquoi, en plus de cela, on peut dire qu’en Python, tout est dictionnaire.