1. Python - Le Langage
1.1. Historique
Le langage Python a été créé à partir de 1980 par Guido van Rossum et le
nom Python fait référence au Monty Python's Flying Circus.
La première version (Python 0.9.0) date de Février 1991 et il a évolué jusqu'à la version 3 (Décembre 2008) pour gagner en
maturité. Il est souvent enseigné comme premier langage de programmation, grâce à sa lisibilité et sa simplicité malgré
quelques défauts liés à sa syntaxe et sa lenteur d'interprétation, son comportement erratique.
Python doit son succès :
- à sa syntaxe claire et lisible
- à un large écosystème de bibliothèques couvrant des domaines variés comme l'intelligence artificielle,
le calcul scientifique, ou encore le développement web
- une communauté dynamique, en effet, Python bénéficie d'une communauté active qui contribue à son développement
et à son adoption
- au fait qu'il permet de se concentrer sur les traitement des données et non sur des tâches de bas
niveau (allocation mémoire, contrôle des indices des tableaux/listes)
Voici rapidement quelques spécificités du langage :
- le langage est interprété
- le typage est dynamique
- la casse est discriminatoire : on distingue majuscules et minuscules ce qui implique que x et X sont deux choses différentes
- on utilise les tabulations (ou les espaces) pour indenter le code et faire apparaître sa
structure
1.2. Documentation
1.3. Editeur
L'indentation en Python est primordiale car elle fait apparaître la structure du code mais est
également utilisée pour savoir à quel bloc une instruction appartient.
Certain éditeurs remplacent les tabulations par des espaces ce qui peut amener Python à
générer des erreurs de syntaxe.
Il est donc conseillé d'utiliser toujours le même environnement de développement (IDE).
Parmi les IDE les plus populaires pour Python, on trouve :
- Visual Studio Code (Microsoft, Python : IntelliSense, débogage, Jupyter Notebook)
- PyCharm (JetBrains - Editeur complet avec autocomplétion, débogage,
refactorisation, gestion des tests et virtualenvs
- Eclipse (Eclipse.org avec le plugin PyDev, environnement complet avec autocomplétion, refactorisation et débogage)
- Spyder (Anaconda, Spécialisé pour la science des données et le calcul scientifique)
- Netbeans (Apache - Extensible pour Python avec des plugins)
- Jupyter Notebook / JupyterLab (Interface web interactive pour l'exécution de code, idéal pour les données et la visualisation.
)
1.4. Caractéristiques du langage
1.4.1. langage interprété
On distingue les langages interprétés (PHP, Python, Javascript) des langages compilés (C, C++, Fortran, Pascal).
Pour un langage compilé, un programme appelé compilateur transforme le code en
instructions exécutables par le microprocesseur de l'ordinateur.
Pour un langage interprété, un programme appelé interpréteur lit une instruction,
l'exécute, puis passe à la suivante.
Le constat est qu'un langage compilé génère du code qui s'exécute beaucoup
plus rapidement qu'un langage interprété (cf fractales de Mandelbrot, facteur d'amélioration de 420 / 6 = 70).
On peut, pour mieux appréhender ce concept, utiliser une analogie : imaginons que vous
ne parliez que français et que vous soyez amené à consulter un livre en russe.
Deux possibilités s'offrent à vous :
- soit faire traduire une bonne fois pour toute le livre du russe
vers le français (compilation)
- soit demander à un interpréteur de vous traduire en direct le
passage que vous désirez lire, cela prendra plus de temps et à chaque
fois que vous aurez besoin de lire un passage il faudra faire appel
une nouvelle fois à l'interprète
1.4.2. typage dynamique
En informatique, on distingue le typage dynamique du typage statique.
Le typage consiste à attribuer un type (booléen, entier, chaîne, réel, ...)
à une variable afin de savoir comment traiter la variable.
1 + 2 => 3 int + int -> int
"bonjour " + "Jean-Michel" => "bonjour Jean-Michel" (str + str -> str, concaténation)
1 + "bonjour" => ?
TypeError: unsupported operand type(s) for +: 'int' and 'str'
"bonjour" + 1 =?> "bonjour1"
TypeError: can only concatenate str (not "int") to str
"bonjour" + str(1) => "bonjour1"
- Le typage statique attribue un seul type à une variable pour la durée de son
existence.
- Le typage dynamique consiste à modifier le type d'une variable lorsqu'on
lui affecte une valeur.
x = 1 (entier, int)
y = 3.14 (réel, float)
s = "bonjour" (chaine, str)
Notons que le typage dynamique est une notion qui est associée aux langages interprétés.
Par exemple en Python :
>>> x = 10
>>> type(x)
<class 'int'>
>>> id(x) (adresse de x en mémoire)
11753896
>>> x = 3.14
>>> type(x)
<class 'float'>
>>> x = "coucou"
>>> type(x)
<class 'str'>
>>> x = [1,2,3]
>>> type(x)
<class 'list'>
>>> id(x)
123902002645824
1.5. Avantages et inconvénients
1.5.1. Avantages
- Python est un langage assez simple que l'on apprend facilement
- Python dispose de nombreuses bibliothèques (numpy, scipy, pyplot, pandas, scikitlearn, tensorflow, pytorch, django, ...) : le développement d'une application est rapide
- communauté de développement très importante et active
- portabilité : tout système d'exploitation
1.5.2. inconvénients
- Python est lent car interprété : cependant il peut être amélioré avec des librairies comme Cython, PyPy (Just-In-Time Compiler)
- Python est gourmand en mémoire
- pas forcément adapté à des projets lourds
- comportement erratique (i.e. qui ne manifeste aucune tendance cohérente)
Afficher le code ens/l1/python1/langage/python_erratique.py
# ===================================================================
# créer une matrice
# l'écriture suivante pose problème car 'ligne' est trois fois
# la même variable, donc si on modifie un élément de ligne il
# est répercuté sur les autres lignes
# ===================================================================
ligne = [0, 0, 0, 0, 0]
matrice = [ ligne, ligne, ligne ]
matrice[0][3] = 1
print(matrice)
# [[0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0]]
# ===================================================================
# oubli
# au lieu d'écrire t[i] == True, on a écrit :
# mais Python n'indique aucune erreur !!
# ===================================================================
i = 1
if [i] == True:
print("ici")
else:
print("non, ici !")
# ===================================================================
#
# modification d'un élément répercutée sur la liste passée en
# paramètre dans f(x) : ok
#
# ===================================================================
def f(x):
x[3] = 99
z = [0 for _ in range(5)]
print(z) # [0, 0, 0, 0, 0]
f(z)
print(z) # [0, 0, 0, 99, 0] OK !
# ===================================================================
#
# modification de la structure non répercutée sur la liste x : ko
#
# ===================================================================
def power(x, d):
if d <= 0:
for i in range(len(x)):
x[i] = 0
x = x[d:] # x = [0,99,0]
x = x + [0] * d # x = [0,99,0,0,0]
# mais manque un "return x"
power(z, 2)
print(z) # [0, 0, 0, 99, 0] au lieu de [ 0, 99, 0, 0, 0]
# ===================================================================
#
# objet mutable en paramètre
#
# ===================================================================
def func(a, L=[]):
for i in range(a):
L.append(i)
return L
# premier appel, L est créée puis vaut [0,1]
print(func(2))
# second appel, L n'est pas recréée donc L = [0, 1, 0, 1, 2]
print(func(3))
1.6. Les types de données
Nous allons nous intéresser principalement aux types de données suivants :
- booléens (
bool) : True, False
- entiers (
int) : 1234, -789999
- range (intervalle entiers -
range(a,b,c)) : range(1,10), range(20,1,-1)
- chaînes de caractères (
"str") : "bonjour", f"x={x}", b"hello"
- nombres flottants (réels -
float)
- listes ($≈$ tableaux
[ list ])
- les tuples (listes non modifiables
( tuple ))
- dictionnaires (mapping -
{ dict })
- ensembles (
{ set })
Il existe également d'autres types que nous ne verrons pas dans ce cours :
complex, function, frozenset, bytes, bytearray, memoryview, ...
1.6.1. Conversion de type
Il suffit généralement de préfixer la valeur avec le nom du type :
# chaine vers entier
entier = int("125")
# entier vers réel
reel = float(123)
# réel vers entier => 3
entier = int(3.14)
# ensemble vers liste
liste = list({1,2,3})
# liste vers ensemble => { 1,2,3 }
ensemble = set([1, 2, 3, 3, 3, 2, 1, 3])
1.6.2. Booléens (bool)
Les booléens sont des valeurs représentant la vérité : True ou False.
# Exemple
est_vrai = True
print(type(est_vrai)) # Résultat : <class 'bool'>
1.6.3. Entiers (int)
Les nombres entiers représentent des valeurs numériques sans partie décimale.
# Exemple
nombre = 42
print(type(nombre)) # Résultat : <class 'int'>
1.6.4. Range ou intervalle d'entiers
range(b) représente l'intervalle d'entiers $[0,b[$ de $0$ à $b-1$
range(a,b) représente l'intervalle d'entiers $[a,b[$, de $a$ à $b-1$
range(a,b,c) représente l'intervalle d'entiers $[a,b[$ tel que les nombres qu'il représente
sont $a + i × c$ jusqu'à $b-1$
range est utilisé avec les boucles for.
# Exemple
r = range(10)
print(list(r)) # Résultat : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
r = range(1,10)
print(list(r)) # Résultat : [1, 2, 3, 4, 5, 6, 7, 8, 9], <class 'range'>
r = range(4,1,-1)
print(list(r)) # Résultat : [4,3,2]
1.6.5. Nombres flottants (float)
Les flottants représentent des nombres réels (cf. flottants).
# Exemple
nombre_decimal = 3.14
epsilon = 8.91e-7
print(type(nombre_decimal)) # Résultat : <class 'float'>
Dans la version 3 de Python, on utilise la représentation IEEE 754 pour les nombres en virgule flottante
en double précision :
- valeurs maximales : $±1.7976931348623157×10^308$ (
sys.float_info.max)
- valeurs minimales : $±2.2250738585072014×10^{−308}$ (
sys.float_info.min)
- précision de 15 à 17 chiffres significatifs
1.6.6. Chaînes de caractères (str)
Les chaînes sont utilisées pour représenter du texte.
# Exemple
texte = "Bonjour, Python !"
print(type(texte)) # Résultat : <class 'str'>
Les chaînes sur plusieurs lignes sont introduites par des triples guillemets simples ou doubles :
# Exemple
une_longue_chaine = '''Bonjour, Python !
voici une chaine sur
plusieurs lignes'''
print(type(une_longue_chaine)) # Résultat : <class 'str'>
Un caractère est une chaîne composée d'un seul caractère ou de longueur 1.
On obtient la longueur d'une chaîne grâce à la fonction len().
Il existe deux fonctions importantes pour les caractères :
ord("c") donne la valeur ASCII/UTF-8 du caractère
chr(int) transforme l'entier en un caractère
ord("A")
65
ord("€")
8364
print(chr(65))
A
print(chr(8364))
€
Le package string contient un nombre important de fonctions
qui permettent de gérer les chaînes de caractères.
Le type string dispose également de nombreuses fonctions (cf lien) :
split, strip, upper, lower, swapcase, startswith, join, isalpha, isdigit, ...
Enfin, il existe deux autres types de chaînes de caractères :
- les b-strings (ou chaînes d'octets) sont créées en ajoutant un préfixe b avant les guillemets : b"...".
Elles contiennent une séquence brute d'octets (valeurs entre 0 et 255) qui ne sont pas directement compatible avec les chaînes
unicode.
- les f-strings (formatted - introduites dans Python 3.6) permettent d'insérer des expressions Python directement dans une
chaîne en utilisant des accolades {}. Elles sont créées avec un préfixe f ou F.
x = 1
pi = 3.14
print(f"{x} {pi/60} {f(3)}") # Résultat 1 0.052333333333333336 9
Par exemple on peut formater les nombres de différentes manières :
x=3
y=4
pi=3.1415927
# x sur 6 caractères, y sur 6 caractères mais précédé par des 0
# pi avec 4 chiffres après la virgule (arrondi)
print(f"{x:6d} {y:06d} {pi:.4f}")
# donne
# ␣␣␣␣␣3 000004 3.1416
1.6.7. Listes (list)
Les listes sont des collections ordonnées et modifiables. On peut aussi les qualifier de tableaux car on
peut en accéder les éléments par leur indice entier.
Une liste peut contenir des éléments de types différents.
# Exemple
ma_liste = [1, 2, 3, "Python", True]
print(type(ma_liste)) # Résultat : <class 'list'>
print(ma_liste[3]) # Résultat : Python
On obtient la longueur d'une liste grâce à la fonction len().
# Exemple
ma_liste = [1, 2, 3, "Python", True]
print(len(ma_liste)) # Résultat 5
1.6.7.a vérification de la présence d'un élément dans la liste
On utilise in pour vérifier la présence de l'élément ou not in pour
vérifier l'absence de l'élément.
Afficher le code ens/l1/python1/langage/liste_presence.py
l = [1, 3, 4, 7]
if 3 in l:
print("présence")
if 5 not in l:
print("absence")
présence
absence
1.6.7.b ajout d'un élément en début de liste
On utilise soit la méthode insert(indice,élément), soit la concaténation de listes :
Afficher le code ens/l1/python1/langage/liste_insere_debut.py
liste = [ "peugeot", "renault"]
# utilisation de la méthode insert
liste.insert(0, "fiat")
print(liste)
# utilisation de la concaténation de listes
liste = ["audi"] + liste
print(liste)
['fiat', 'peugeot', 'renault']
['audi', 'fiat', 'peugeot', 'renault']
1.6.7.c ajout d'un élément en fin de liste
On utilise soit la méthode append(élément), soit la concaténation de listes :
Afficher le code ens/l1/python1/langage/liste_insere_fin.py
liste = [ "peugeot", "renault"]
# utilisation de append()
liste.append("toyota")
print(liste)
# utilisation de la concaténation de liste
liste = liste + ["volkswagen"]
print(liste)
['peugeot', 'renault', 'toyota']
['peugeot', 'renault', 'toyota', 'volkswagen']
1.6.7.d ajout d'un élément dans la liste
On utilise soit la méthode insert(indice,élément), soit la concaténation de listes :
Afficher le code ens/l1/python1/langage/liste_insere_milieu.py
liste = [ "audi", "fiat", "peugeot", "renault"]
liste.insert(1, "citroën")
print(liste)
liste = [ "audi", "fiat", "peugeot", "renault"]
liste = liste[:2] + ["hyundai"] + liste[-2:]
print(liste)
['audi', 'citroën', 'fiat', 'peugeot', 'renault']
['audi', 'fiat', 'hyundai', 'peugeot', 'renault']
1.6.7.e suppression d'un élément en début de liste
On utilise soit l'instruction del, soit la méthode pop(0) :
Afficher le code ens/l1/python1/langage/liste_supprime_debut.py
liste = [ "audi", "fiat", "peugeot", "renault"]
del liste[0]
print(liste)
liste.pop(0)
print(liste)
['fiat', 'peugeot', 'renault']
['peugeot', 'renault']
1.6.7.f suppression d'un élément en fin de liste
On utilise soit l'instruction del, soit la méthode pop() (sans argument) :
Afficher le code ens/l1/python1/langage/liste_supprime_fin.py
liste = [ "audi", "fiat", "peugeot", "renault"]
del liste[-1]
print(liste)
liste.pop()
print(liste)
['audi', 'fiat', 'peugeot']
['audi', 'fiat']
1.6.7.g suppression d'un élément dans la liste
On utilise soit l'instruction del, soit la méthode pop(n) (où $n$ est l'indice de l'élément), soit
la méthode remove(élément) :
Afficher le code ens/l1/python1/langage/liste_supprime_milieu.py
liste = [ "audi", "fiat", "peugeot", "renault", "volkswagen" ]
# supprimer l'élément suivant sa valeur
liste.remove("peugeot")
print(liste)
# supprimer l'élément suivant son indice avec del
del liste[2]
print(liste)
# supprimer l'élément suivant son indice avec pop
liste.pop(1)
print(liste)
['audi', 'fiat', 'renault', 'volkswagen']
['audi', 'fiat', 'volkswagen']
['audi', 'volkswagen']
1.6.7.h Création de listes par compréhension
La création de listes par compréhension en Python (ou list comprehension) est une
syntaxe concise et élégante (qui s'apparente à la syntaxe du langage naturel) permettant de
générer des listes à partir d'itérables, tout en appliquant des transformations ou des filtres.
Elle remplace souvent une boucle for classique pour rendre le code plus lisible et plus compact.
La syntaxe est la suivante :
[ nouvel_élément for élément in iterable if condition]
La partie if condition est optionnelle.
Par exemple on peut écrire :
liste = [x for x in range(5)]
print(liste) # [0, 1, 2, 3, 4]
au lieu de :
liste = []
for x in range(5):
liste.append(x)
Voici un exemple avec deux for :
combinaisons = [(x, y) for x in range(2) for y in range(2)]
print(combinaisons) # [(0, 0), (0, 1), (1, 0), (1, 1)]
Afin de créer une liste d'éléments constants, on peut écrire :
liste_de_zeros = [0 for _ in range(5)]
print(liste_de_zeros) # Résultat : [0, 0, 0, 0, 0]
Voici un autre exemple avec un for et un if :
pairs = [x for x in range(10) if x % 2 == 0]
print(pairs) # [0, 2, 4, 6, 8]
1.6.8. Tuples (tuple)
Les tuples sont des collections ordonnées mais immuables, ce sont des listes non modifiables.
Elles sont introduites par (...).
# Exemple
mon_tuple = (1, 2, 3, "Python")
print(type(mon_tuple)) # Résultat : <class 'tuple'>
print(t[0]) # Résultat : 1
print(t[3]) # Résultat : Python
1.6.9. Dictionnaires (dict)
Les dictionnaires stockent des paires clé-valeur : à une clé est associée une valeur.
Ils sont introduits par {...}.
# Exemple
mon_dico = {"nom": "Alice", "âge": 30}
print(type(mon_dico)) # Résultat : <class 'dict'>
print(mon_dico["nom"]) # Résultat : "Alice"
- on peut accéder les clés grâce à
.keys()
- on peut accéder les valeurs grâce à
.values()
- on peut accéder les (clés,valeurs) grâce à
.items()
mon_dico = { 1: "rouge", 2:"vert", 3:"bleu", 0:"noir" }
print(mon_dico.values())
# Résultat : dict_values(['rouge', 'vert', 'bleu', 'noir'])
print(mon_dico.keys())
# Résultat : dict_keys([1, 2, 3, 0])
print(mon_dico.items())
# Résultat : dict_items([(1, 'rouge'), (2, 'vert'), (3, 'bleu'), (0, 'noir')])
1.6.9.a vérification de la présence d'un élément dans un dictionnaire
On utilise in pour vérifier la présence et not in pour
vérifier l'absence :
Afficher le code ens/l1/python1/langage/dict_presence.py
dico = { 3: "bleu", 0 : "noir", 2 : "vert", 1: "rouge" }
cle = 3
if cle in dico:
print(dico[cle])
cle = 7
if cle not in dico:
print("clé absente")
bleu
clé absente
1.6.9.b ajout d'un élément dans un dictionnaire
On utilise dict[clé]=valeur ou la méthode update :
Afficher le code ens/l1/python1/langage/dict_ajoute.py
dico = { 3: "bleu" }
dico.update({ 0 : "noir", 2 : "vert" })
print(dico)
dico[1] = "rouge"
print(dico)
{3: 'bleu', 0: 'noir', 2: 'vert'}
{3: 'bleu', 0: 'noir', 2: 'vert', 1: 'rouge'}
1.6.9.c suppression d'un élément dans un dictionnaire
On utilise del ou la méthode pop() qui retourne la
valeur associée à la clé :
Afficher le code ens/l1/python1/langage/dict_supprime.py
dico = { 3: "bleu", 0 : "noir", 2 : "vert", 1: "rouge" }
del dico[3]
print(dico)
valeur = dico.pop(1)
print(valeur)
print(dico)
{0: 'noir', 2: 'vert', 1: 'rouge'}
rouge
{0: 'noir', 2: 'vert'}
1.6.10. Ensembles (set)
Les ensembles sont des collections non ordonnées de valeurs uniques.
# Exemple
mon_ensemble = {1, 2, 3, 3, 2, 1, 3}
print(mon_ensemble) # Résultat : {1, 2, 3}
print(type(mon_ensemble)) # Résultat : <class 'set'>
print(mon_ensemble[0]) # Résultat : 1
Les éléments d'un ensemble ne sont pas manipulable grâce à l'opérateur [], il faut transformer l'ensemble en
liste ou alors parcourir les éléments de l'ensemble grâce à une boucle for.
1.6.10.a vérification de la présence d'un élément dans un ensemble
On utilise in pour vérifier la présence et not in pour
vérifier l'absence :
Afficher le code ens/l1/python1/langage/set_presence.py
ensemble = { 1, 3, 5}
if 3 in ensemble:
print("3 est présent")
else:
print("3 n'est pas présent")
if 4 not in ensemble:
print("4 n'est pas présent")
else:
print("4 est présent")
3 est présent
4 n'est pas présent
1.6.10.b ajout d'un élément dans un ensemble
On utilise :
- soit la méthode
add(élement)
- soit la méthode
update({élements})
Afficher le code ens/l1/python1/langage/ensemble_ajoute.py
ensemble = { 1, 2, 3}
ensemble.add(4)
print(ensemble)
ensemble.update({1, 3, 4, 5, 6})
print(ensemble)
{{1, 2, 3, 4}
{1, 2, 3, 4, 5, 6}
1.6.10.c suppression d'un élément dans un ensemble
On utilise :
- soit la méthode
remove(élement)
- soit la méthode
discard(élement) qui ne lève pas d'erreur si l'élément n'est
pas présent
- soit la méthode
pop() qui supprime un élément quelconque
Afficher le code ens/l1/python1/langage/ensemble_manipuler.py
mon_ensemble = set([ x for x in range(0,16) if x % 2 != 0])
print(mon_ensemble)
if 3 in mon_ensemble:
print("3 est dans l'ensemble")
mon_ensemble.remove(3)
if 3 not in mon_ensemble:
print("3 n'est pas dans l'ensemble")
mon_ensemble.discard(3)
element = mon_ensemble.pop()
print(element)
for i, x in enumerate(mon_ensemble):
print(f"{i:2} => {x}")
{1, 3, 5, 7, 9, 11, 13, 15}
3 est dans l'ensemble
3 n'est pas dans l'ensemble
1
0 => 5
1 => 7
2 => 9
3 => 11
4 => 13
5 => 15
1.6.11. Les fichiers
Les fichier permettent de sauvegarder de l'information sur le disque (HDD/SSD).
On utilise plusieurs notions pour la lecture ou l'écriture des fichiers. notamment :
with open(nom_fichier, mode) as variable
- les exceptions pour intercepter les erreurs éventuelles (fichier absent, permissions)
Le paramètre mode peut prendre plusieurs valeurs ou la combinaison de ces valeurs :
- b : fichier binaire
- t : fichier texte (par défaut)
- r : ouverture en lecture
- w : ouverture en écriture avec remise à zéro du fichier
- a : ouverture en écriture avec ajout en fin du fichier (append)
1.6.11.a Ecriture dans un fichier texte
On utilise la fonction write :
Afficher le code ens/l1/python1/langage/exemple_ecriture_fichier.py
chaine = '''Bonjour le monde !
lorem ipsum dolor
'''
try:
# ouverture d'un flux en écriture 'w'
with open("texte.txt", "w") as fichier:
fichier.write(chaine)
print(type(fichier))
fichier.close()
# gestion des exceptions liées à 'try:'
except FileNotFoundError:
print("fichier non existant")
except PermissionError as e2:
print(f"{e2}")
except Exception as e3:
print(f"Une erreur est survenue : {e3}")
<class '_io.TextIOWrapper'>
1.6.11.b Lecture d'un fichier texte
On utilise read() pour lire la totalité du fichier sous forme d'une chaîne ou readlines()
qui retourne une liste de lignes.
Afficher le code ens/l1/python1/langage/exemple_lecture_fichier.py
try:
nom_fichier = "texte.txt"
# ouverture d'un flux en lecture 'r'
with open(nom_fichier, 'r', encoding='utf-8') as fichier:
# lecture du fichier texte comme une seule chaine
#texte = fichier.read()
# ou lecture comme plusieurs lignes
texte = fichier.readlines()
print(texte)
print(type(fichier))
fichier.close()
# gestion des exceptions liées à 'try:'
except FileNotFoundError as e1:
print(f"{e1}")
print(f"fichier non trouvé {nom_fichier}")
except Exception as e2:
print(f"Une erreur est survenue : {e2}")
['Bonjour le monde !\n', 'lorem ipsum dolor\n']
<class '_io.TextIOWrapper'>
1.7. Les instructions
1.7.1. L'instruction if-elif-else
Le if appelée conditionnelle permet de réaliser un traitement si une condition est vraie :
Afficher le code ens/l1/python1/langage/exemple_if.py
a = 2
b = 0
if a > 0 and b < 10:
print("valeurs valides")
elif b == 0:
print("division par 0 impossible")
else:
print("valeurs non valides")
valeurs valides
1.7.2. L'instruction match
Le match (encore appelé switch ou case of dans d'autres langage) et l'équivalent d'un if
mais ne s'applique la plupart du temps qu'à des valeurs scalaires comme les entiers :
Afficher le code ens/l1/python1/langage/exemple_match.py
a = 2
match a:
case 1:
print("a=1")
case 2:
print("a=2")
case 3:
print("a=3")
case _:
print("valeur non valide")
a=2
1.7.3. L'instruction while
Le while permet de réaliser le même traitement en boucle tant qu'une condition est vraie :
Afficher le code ens/l1/python1/langage/exemple_while.py
i = 1
do_not_exit = True
while i < 10 and do_not_exit:
print(i)
i = i + 3
if i * i > 20:
do_not_exit = False
1
4
1.7.4. L'instruction for
Le for est utilisé pour parcourir containers (list, dict) ou des itérateurs (range) :
Afficher le code ens/l1/python1/langage/exemple_for.py
# i = 0, .., 9
for i in range(10):
print(i)
# i = 1, .., 9
for i in range(1,10):
print(i)
l = ['renault','peugeot','fiat','volkswagen','audi']
# 'renault','peugeot',.., audi
for marque in l:
print(marque)
# 0 renault, 1 peugeot, .., 4 audi
for cle, marque in enumerate(l):
print(cle, marque)
dico = { 0: 'noir', 1:'bleu', 2:'rouge', 3:'vert'}
# 0 noir, 1 bleu, .., 3 vert
for cle in dico:
print(cle, dico[cle])
# 0 noir, 1 bleu, .., 3 vert
for cle, valeur in dico.items():
print(cle, valeur)
0
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
renault
peugeot
fiat
volkswagen
audi
0 renault
1 peugeot
2 fiat
3 volkswagen
4 audi
0 noir
1 bleu
2 rouge
3 vert
0 noir
1 bleu
2 rouge
3 vert
1.7.5. L'instruction print
Elle permet l'affichage d'information à l'écran ou l'enregistrement d'information dans un fichier
au format texte. Son prototype est le suivant :
print(*args, sep=' ', end='\n', file=None, flush=False)
On affiche un ensemble d'arguments (args) qui sont des entiers, des flottant, decs chaînes, etc.
sep représente le séparateur entre les arguments, par défaut c'est un espace
end est le caractère affiché après avoir réalisé print, par défaut c'est le retour
à la ligne mais on peut le remplacer par un caractère vide end='' pour continuer à
écrire sur la même ligne
file est un fichier si on ne veut pas écrire sur la sortie standard
flush indique si on doit vider le flux d'affichage après print
Afficher le code ens/l1/python1/langage/exemple_print.py
print("Bonjour", "le", sep='***', end='')
print("--monde--", end='!\n')
Bonjour***le--monde--!
1.7.6. L'instruction input
Elle permet de récupérer une chaîne saisie au clavier et terminée par un retour chariot qui ne sera pas
pris en compte. Son format est le suivant :
input(prompt='')
Afficher le code ens/l1/python1/langage/exemple_input.py
chaine = input("saisir un entier:")
entier = int(chaine)
print(type(chaine), type(entier))
print(entier)
chaine = input("saisir 3 prénoms: ")
print(chaine)
saisir un entier:70
<class 'str'> <class 'int'>
70
saisir 3 prénoms: jean-michel karla sarah
jean-michel karla sarah
1.8. Les sous-programmes (procédures et fonctions)
Un sous-programme est introduit par le mot clé def suivi du nom du sous-programme et de ses paramètres :
- une fonction est un sous-programme qui retourne une valeur
- une procédure est un sous-programme qui ne retourne pas de valeur : la valeur obtenue sera
None
Afficher le code ens/l1/python1/langage/exemple_fonction.py
def ma_fonction(x, y):
return x + y
print(ma_fonction(1,2))
Il est nécessaire d'ajouter des commentaires aux sous-programmes afin d'indiquer à d'autres personnes qui
voudraient les utiliser, comment ils fonctionnent :
Afficher le code ens/l1/python1/langage/exemple_fonction_detaillee.py
def addition( x, y : int) -> int:
"""
Calcule la somme de x et y
"""
return x + y
def car(l : list[int]) -> int:
"""
Implantation de la fonction Lisp car (Contents of
the Address part of the Register) qui retourne
la liste passée en paramètre sans son premier
élément
Si la liste est vide une exception est levée.
"""
if len(l) == 0:
raise ValueError("liste vide")
return l[0]
def cdr(l : list[int]) -> int:
"""
Implantation de la fonction Lisp cdr (Contents of
the Decrement part of the Register) qui retourne
la liste passée en paramètre sans son premier
élément
Si la liste est vide une exception est levée.
"""
if len(l) == 0:
raise ValueError("liste vide")
x = l.pop(0)
return l
x = 1
y = 2
print(f"{x}+{y} = ", addition(x,y))
ma_liste=[1,2,3]
premier = car(ma_liste)
ma_liste = cdr(ma_liste)
print(f"premier élément de la liste = {premier}")
print(f"liste sans son premier élément = {ma_liste}")
1.8.1. Valeur de retour
Une fonction peut retourner plusieurs valeurs :
Afficher le code ens/l1/python1/langage/exemple_fonction_retour.py
def quotient_reste(x, y):
return x // y, x % y
# a=quotient, b=reste
a, b = quotient_reste(77,8)
print(a,b)
# (quotient,reste) tuple
t = quotient_reste(59,8)
print(t)
# ignore la première valeur de retour
_, b = quotient_reste(59,8)
print(b)
on récupère les résultats dans deux variables différentes
9 5
on récupère les résultats sous forme d'un tuple
(7, 3)
on ne récupère que le reste
3
1.8.2. Passage par valeur et par adresse
En Python, il n'existe pas de passage de paramètres par adresse à proprement parler, comme dans des langages comme C ou C++.
Cependant, Python utilise un mécanisme appelé passage par objet-référence qui implique que :
- les objets mutables (comme les listes, dictionnaires, ou objets personnalisés) peuvent être modifiés directement
dans une fonction, ce qui simule un comportement proche du passage par adresse
- les objets immuables (comme les entiers, chaînes de caractères, ou tuples) ne peuvent pas être modifiés
directement dans une fonction. Si vous modifiez leur valeur, cela crée un nouvel objet, et la modification ne s'applique
pas à la variable d'origine
Pour simuler un passage par adresse avec des objets immuables, on peut encapsuler la valeur dans un objet mutable
comme une liste ou un dictionnaire :
Afficher le code ens/l1/python1/langage/passage_par_adresse.py
def ma_fonction(param):
"""
Simulation d'un passage par adresse
"""
param[0] += 1
valeur = 10
parametre = [valeur]
ma_fonction(parametre)
valeur = parametre[0]
# Affiche 11
print(valeur)
1.8.3. Typage des paramètres et du type de retour
Le typage des paramètres et des valeurs de retour des fonctions a pour objectif :
- d'améliorer la qualité du code (lisibilité, robustesse, maintenabilité)
- de faciliter la collaboration et la documentation
- d'aider à prévenir les erreurs grâce à l’analyse statique
Cependant, le typage en Python :
- est facultatif et n’est pas enforçable à l’exécution
- nécessite des outils externes comme mypy pour tirer pleinement partie de ses avantages
- peut ajouter un peu de complexité ou de verbosité, surtout pour les fonctions complexes
Par exemple dans le code suivant, bien qu'on ait spécifié que la fonction addition prenait des
paramètres entiers, il est toujours possible de passer des chaînes :
Afficher le code ens/l1/python1/langage/non_enforcable_a_l_execution.py
def addition(a: int, b: int) -> int:
return a + b
# Appel avec des types inattendus
print(addition("1", "2")) # Résultat : "12" (concaténation de chaînes)
Voici quelques exemples de typage de base ainsi que l'utilisation du package typing :
Afficher le code ens/l1/python1/langage/exemple_typage.py
# -----------------------------------------------------
# définition du type des paramètres
def enregistrer_utilisateur(nom: str, age: int, actif: bool) -> None:
pass
# -----------------------------------------------------
# cas d'un dictionnaire
def transformer_dict(data: dict[str, int]) -> dict[int, str]:
return {value: key for key, value in data.items()}
# -----------------------------------------------------
# utilisation de tuple
def extraire_coordonnees(coordonnees: tuple[float, float]) -> float:
x, y = coordonnees
return (x**2 + y**2)**0.5
# -----------------------------------------------------
# typage avec liste d'entiers
def afficher_nombres(nombres: list[int] | None) -> None:
if nombres is None:
print("Pas de nombres à afficher.")
else:
print(nombres)
# -----------------------------------------------------
# union de valeurs
def calculer(valeur: int | float) -> float:
return valeur ** 2
# -----------------------------------------------------
# Any pour les listes génériques
from typing import Any, List
def afficher_elements(elements: List[Any]) -> None:
for element in elements:
print(element)
# -----------------------------------------------------
# combiner List et Union
from typing import Union, List
def traiter_donnees(donnees: List[Union[int, str]]) -> None:
for donnee in donnees:
if isinstance(donnee, int):
print(f"Nombre : {donnee}")
else:
print(f"Texte : {donnee}")
# -----------------------------------------------------
# utilisation de Dict
from typing import Dict
def process_users(users: Dict[str, int]) -> None:
for name, age in users.items():
print(f"{name} a {age} ans")
# -----------------------------------------------------
# utilisation de Literal
from typing import Literal
def choisir_action(action: Literal["start", "stop", "pause"]) -> None:
print(f"Action choisie : {action}")
# -----------------------------------------------------
# utilisation de TypeVar pour liste générique
from typing import TypeVar
T = TypeVar("T")
def inverser_liste(elements: list[T]) -> list[T]:
return elements[::-1]
1.8.4. Les fonctions intégrées
Une fonction intégrée est une fonction propre au langage et peut parfois être appliquée à différents types :
1.8.4.a Les fonctions de base
len() : renvoie la longueur d'un objet (list, chaîne, dictionnaire)
type() : renvoie le type de l'objet
print() : affiche un message ou une valeur
id() : renvoie l'identifiant unique d'un objet
isinstance() : vérifie si un objet est une instance d'une classe
1.8.4.b Les fonctions numériques
abs() : valeur absolue
round() : arrondi au plus proche
pow() : exponentiation (équivalent à **)
divmod() : renvoie le quotient et le reste de la division
min(), max() : valeurs minimale et maximale
sum() : somme des éléments d'un itérable
1.8.4.c Les fonctions liées aux chaînes et objets itérables
all() : renvoie True si tous les éléments d'un itérable sont vrais
any() : renvoie True si au moins un élément est vrai
sorted() : renvoie une version triée d'un itérable
reversed() : renvoie un itérable inversé
enumerate() : renvoie un itérable avec indices
filter(): filtre les éléments d'un itérable.
map() : applique une fonction à tous les éléments d'un itérable
zip() : regroupe plusieurs itérables
1.8.4.d Les fonctions liées aux types
int(), float(), str(), bool() : conversions de types
list(), tuple(), set(), dict() : crée ou convertit des types de collections
bytes(), bytearray() : manipulation des données binaires
complex() : crée un nombre complexe
1.8.4.e Les fonctions liées aux entrées / sorties
open() : ouvre un fichier
input() : lit une chaîne au clavier
exit(), quit() : quitte le programme
1.9. Packages et import
Lorsque l'on crée un fichier Python, l'ensemble des variables globales et sous-programmes qu'il définit
peuvent être réutilisés dans un autre fichier.
Imaginons que vous avons le fichier suivant fonction.py :
Afficher le code ens/l1/python1/langage/fonction.py
"""
Package de calcul : définition d'une fonction f(x) et
d'une fonction f_prime qui calcule la dérivée pour
une fonction f(x) existante
"""
# définition d'une constante
EPSILON = 1e-8
def f(x : float) -> float:
"""
Calcule f(x) = x**2 pour tout x flottant
"""
return x**2
def f_prime(x : float) -> float:
"""
Calcule la dérivée de f(x) grâce à la formule
f'(x) = (f(x+epsilon) - f(x)) / epsilon
"""
return (f(x+EPSILON) - f(x)) / EPSILON
Afin d'utiliser dans un autre fichier Python, les sous-programmes qu'il définit, trois possibilités
s'offrent à nous :
- utiliser le nom du fichier en tant que préfixe de référence
- renommer le nom du fichier pour utiliser un autre préfixe : en général plus court pour avoir moins à écrire
- importer l'ensemble des fonctions et variables globales et y faire référence sans préfixe : il ne faut pas
qu'il y ait de conflit avec un autre fichier
1.9.1. import du fichier
Afficher le code ens/l1/python1/langage/evalue_fonction_v1.py
# import du fichier mais utilisation du nom du package
# en tant que préfixe pour accèder aux sous-programmes
# du fichier fonction.py
import fonction
import numpy as np
x = np.arange(-2.0 ,2.0, 0.5)
print(x)
# on utilise le préfixe 'fonction' devant f(x) et f_prime(x)
y = fonction.f(x)
y_prime = fonction.f_prime(x)
for i in range(len(y)):
print(f"{x[i]:5.3f} {y[i]:5.2f} {y_prime[i]}")
[-2. -1.5 -1. -0.5 0. 0.5 1. 1.5]
-2.000 4.00 -3.999999975690116
-1.500 2.25 -2.999999981767587
-1.000 1.00 -1.9999999989472883
-0.500 0.25 -0.9999999883714139
0.000 0.00 1e-08
0.500 0.25 1.0000000161269895
1.000 1.00 1.999999987845058
1.500 2.25 2.999999981767587
1.9.2. import du fichier avec renommage
Afficher le code ens/l1/python1/langage/evalue_fonction_v2.py
# import du fichier et renommage du fichier en tant que 'fn'
# utilisé en tant que préfixe pour accèder aux sous-programmes
# du fichier fonction.py
import fonction as fn
import numpy as np
x = np.arange(-2.0 ,2.0, 0.5)
print(x)
# on utilise le préfixe 'fn' devant f(x) et f_prime(x)
y = fn.f(x)
y_prime = fn.f_prime(x)
for i in range(len(y)):
print(f"{x[i]:5.3f} {y[i]:5.2f} {y_prime[i]}")
1.9.3. import de tous les éléments du fichier
Afficher le code ens/l1/python1/langage/evalue_fonction_v3.py
# import de tous les éléments (constantes et sous-programmes)
# du fichier fonction.py de manière à ne pas utiliser de préfixe
from fonction import *
import numpy as np
x = np.arange(-2.0 ,2.0, 0.5)
print(x)
# on utilise directement f(x) et f_prime(x)
y = f(x)
y_prime = f_prime(x)
for i in range(len(y)):
print(f"{x[i]:5.3f} {y[i]:5.2f} {y_prime[i]}")
1.9.4. version d'un package
Certains packages nécessitent une version particulière d'un autre package pour pouvoir fonctionner correctement.
On peut connaître la version d'un package :
Soit en ligne de commande :
\$ pip list | grep numpy
numpy 1.26.4
Soit dans l'interpréteur Python ou n exécutant un programme :
\$ python3
Python 3.12.3 (main, Nov 6 2024, 18:32:19) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> print(numpy.__version__)
1.26.4
1.10. Amélioration de l'efficacité du code
Voici un petit exemple qui montre comment utiliser pythran afin
d'améliorer le temps d'exécution d'un programme Python.
Pythran est un compilateur Python vers C++ conçu pour accélérer les calculs numériques et
optimiser les performances des programmes Python, en particulier ceux utilisant des tableaux NumPy.
Il transforme des fonctions Python annotées en extensions Python compilées en C++,
ce qui réduit considérablement le temps d'exécution pour des tâches computationnelles lourdes.
1.10.1. Sans pythran
On considère le module suivant qui fournit une fonction somme_sin_cos :
On importe le package math de Python.
Afficher le code ens/l1/python1/langage/calcul.py
import math
# pythran export somme_sin_cos(float64[])
def somme_sin_cos(t):
"""
Calcul de la somme des sin(x)*cos(x)
pour tout x de la liste t
"""
total = 0.0
for x in t:
total += math.sin(x) * math.cos(x)
return total
Ainsi que le programme qui utilise cette fonction :
Afficher le code ens/l1/python1/langage/utilisation_calcul.py
import numpy as np
import calcul # The compiled module
import time
# tableau de 100 millions de valeurs aléatoires
tableau = np.random.rand(100000000)
debut = time.time()
resultat = calcul.somme_sin_cos(tableau)
fin = time.time()
print("temps d'exécution=", (fin-debut))
print(f"résultat={resultat}")
Si on exécute simplement ce programme en Python sur un AMD Ryzen 5 5600G, on obtient :
\$ time python3 utilisation_calcul.py
temps d'exécution= 7.702448606491089
résultat=35401505.8430852
real 0m8,248s
user 0m8,253s
sys 0m0,753s
Soit un temps d'exécution total de $8,253$ secondes dont $7,702$ secondes pour exécuter le code de la
fonction somme_sin_cos.
1.10.2. Avec pythran
Il suffit de précompiler le code grâce à la commande qui suit, ce qui crée le fichier calcul.cpython-312-x86_64-linux-gnu.so :
\$ pythran calcul.py>
\$ ls *.so
calcul.cpython-312-x86_64-linux-gnu.so
Un fichier avec l’extension .so sous Linux est une bibliothèque partagée (Shared Object).
Il s'agit d’un fichier contenant du code compilé et des données qui peuvent être utilisés par plusieurs
programmes en même temps. Ces fichiers sont comparables aux fichiers .dll sur Windows.
Ici cela ne fonctionne uniquement parce qu'on a ajouté une directive :
pythran export somme_sin_cos(float64[])
devant la fonction à optimiser qui indique quel type de donnée est fourni en entrée à la fonction.
Pythran génère alors du code assembleur tout comme le ferait un compilateur C/C++.
Si maintenant on exécute le code comme précédemment, Python va utiliser le fichier objet partagé, mais cela
est transparent pour l'utilisateur :
\$ time python3 utilisation_calcul.py
temps d'exécution= 0.9642660617828369
résultat=35404787.98554713
real 0m1,505s
user 0m1,502s
sys 0m0,761s
On met alors moins d'une seconde pour exécuter la fonction compilée.
Dans le fichier .so, on retrouve le code assembleur qui correspond au calcul :
3d80: movsd xmm0,QWORD PTR [r15] ; charger x ou t[i]
3d85: mov rsi,rbp
3d88: mov rdi,r12
3d8b: add r15,0x8
3d8f: call 2420 < sincos@plt> ; calculer sin(x) et cos(x)
3d94: movsd xmm0,QWORD PTR [rsp+0x18]
3d9a: mulsd xmm0,QWORD PTR [rsp+0x10] ; multiplier sin(x) par cos(x)
3da0: addsd xmm0,QWORD PTR [rsp] ; ajouter au résultat
3da5: movsd QWORD PTR [rsp],xmm0
3daa: cmp rbx,r15 ; continuer tant qu'on est pas
3dad: jne 3d80 <PyInit_calcul@@Base+0xa10> ; à la fin du tableau
3daf: lea rdi,[rsp+0x40]
La fonction sincos appartient normalement à la librairie mathématique et son protoype est
__sincos (double x, double *sinx, double *cosx).
Pour résumer :
| Sans Pythran |
Avec Pythran |
| 7,70s |
0,96s |
Optimisation Pythran sur exemple avec AMD Ryzen 5 5600G
On notera cependant qu'on n'obtient pas le même résultat car les valeurs ne sont pas les mêmes dans les deux cas.