I. Les paradigmes impératif et fonctionnel¶
Cours¶
A. Les éléments de base de la programmation¶
Analysons le code Python suivant :
Code
l = [2, 6, 9]
s = 0
if l[0]%2 == 0:
s = s + l[0]
if l[1]%2 == 0:
s = s + l[1]
if l[2]%2 == 0:
s = s + l[2]
Dans l'exemple ci-dessus, quels sont les éléments de base de la programmation utilisés ?
On identifie des affectations, des instructions conditionnelles. L'ensemble est une séquence d'instructions.
Que fait ce programme ?
Il calcule la somme des éléments pairs d'une liste Python.
Proposer une autre manière d'écrire ce programme. Quel autre élément de programmation utilisez-vous ?
Il est pertinent d'utiliser une boucle bornée.
Programme avec boucle
```python l = [2, 6, 9] s = 0 for i in range(len(l)): if l[i]%2 ==0: s = s+l[i]
Ecrire ce programme en utilisant deux fonctions intermédiaires.
```python def est_pair(n): return n%2 == 0
def somme(l):
s = 0
for i in range(len(l)):
if est_pair(l[i]):
s = s+l[i]
l = [2, 6, 9]
s = somme(l)
Il y a donc plusieurs manières de programmer un même algorithme, en utilisant différemment les éléments de programmation à notre disposition. Un paradigme de programmation est une manière de programmer spécifique.
B. Le paradigme impératif¶
Le paradigme impératif est le paradigme le plus courant, utilisé par de nombreux langages de programmation comme Python (qui intègre aussi d'autres paradigmes !). Il consiste à décrire un programme sous la forme de séquences d'instructions à effectuer dans un ordre précis.
Les caractéristiques de ce paradigme sont :
- importance de l'ordre des instructions,
- utilisation d'affectations de variables, d'expressions conditionnelles et de boucles.
Cette manière de programmer est proche du fonctionnement de la machine et peut sembler la plus intuitive.
Pourquoi utiliser d'autres paradigmes ?
On peut préférer d'autres manières de programmer en fonction du champ d'application du programme, du problème spécifique qui est traité, ou tout simplement des goûts du programmeur.
C. Le paradigme fonctionnel¶
Le paradigme fonctionnel est un paradigme rigoureux, permettant de plus facilement tester, déboguer et optimiser les programmes.
C.1. Les fonctions¶
Rappeler la syntaxe de définition et d'appel d'une fonction en Python :
Code
def nom_de_la_fonction(entree1,entree2):
#séquence d'instructions
(return sortie)
resultat = nom_de_la_fonction(val1, val2)
Le paradigme fonctionnel est centré, comme son nom l'indique, sur l'utilisation de fonctions. Plus précisément, on utilise des fonctions dites "pures" : des fonctions qui, étant donné des paramètres d'entrée, renvoient toujours la même sortie. Pour cela, il ne faut pas qu'elles créent d'effets de bord.
C.2. Les effets de bord¶
Définition
Un effet de bord correspond à la modification par une fonction d'une ou plusieurs variable(s) définie(s) en dehors de cette fonction.
Définition
Une variable définie en dehors d'une fonction est une variable globale, utilisable dans tout le programme. Une variable définie dans une fonction est par défaut locale : elle n'existe que dans cette fonction.
Exemple
Code
l = [0,1,2,3]
def remplace(x):
y = l[0]
l[0] = x
return y
Que fait cette fonction ? En écrire la spécification (description des entrées et sorties).
Cette fonction remplace la valeur du premier élement de l
par une valeur donnée en entrée.
Entrée : entier x
remplaçant la valeur du premier élément de l
Sortie : l'entier y
en première position de la liste l
avant d'être remplacé
Que renvoie l'appel de remplace(1)
? Un deuxième appel de remplace(1)
?
Le premier appel renvoie 0
, alors que le deuxième renvoie 1
.
Que peut-on en conclure ?
Il y a modification de la variable l
définie en dehors de remplace
: elle crée un effet de bord. La fonction, appelée plusieurs fois avec le même paramètre ne renvoie pas la même valeur : ce n'est pas une fonction pure.
Proposer une fonction "pure" effectuant le même traitement que remplace
.
Solution
def remplace(x, l):
l2 = list(l)
y = l2[0]
l2[0] = x
return y, l2
N.B. : une fonction pure renvoie toujours une valeur (sinon, vu qu'elle n'a pas d'effet de bord, elle ne servirait pas à grand chose...).
C.3. Les caractéristiques du paradigme fonctionnel¶
Les caractéristiques de ce paradigme sont :
- utilisation de fonctions imbriquées (cf partie A),
- utilisation de fonctions pures (donc pas d'effet de bord !),
- on évite l'affectation de valeurs à des variables (utilisation de fonctions à la place).
Ces caractéristiques rendent les programmes plus fiables : différentes bonnes pratiques de programmation s'inspirent de cette manière de programmer.
Exercices¶
Exercice 1 :¶
A faire sur feuille.
Dans les exemples suivant, indiquer :
- s'il y a un effet de bord ou non,
- si la fonction est pure,
- s'il y a un effet de bord, proposer une autre version de la fonction qui l'élimine.
1.
Code
i = 5
def f():
return i>5
2.
Code
def somme(a,b):
s = a+b
return s
3.
Code
x = 11
l = [1,3,5,7,9]
def remplace():
if x>l[-1]:
l[-1] = x
return l
Exercice 2 :¶
A faire sur feuille.
Identifier les caractéristiques se rapportant au paradigme impératif, et celles se rapportant au paradigme fonctionnel.
- autorise les effets de bord,
- l'ordre des instructions n'a pas toujours d'importance,
- utilise des affectations de variables,
- utilise des boucles,
- découpe un programme en fonctions.
Exercice 3 :¶
A faire sur ordinateur, réponses sur feuille.
On définit deux fonctions qui ajoutent un élément à une liste Python. Elles appellent toutes les deux la méthode append
, qui modifie la liste.
Code
def ajoute(l, elt):
l.append(elt)
return l
def ajoute1(l, elt):
l.append(elt)
-
Tester
ajoute
en définissant, dans le corps de votre programme, une listel1
qui vaut[0, 1, 2, 3]
, puis en faisant l'appell2 = ajoute(l1, 4)
. Que contiennentl1
etl2
après cet appel ? -
Tester
ajoute1
en faisant l'appelajoute1(l1, 4)
. Où se trouve le résultat de la fonction ?A retenir : Les listes en paramètres des fonctions Python sont passées par référence : on fait référence à la liste initiale, à son emplacement dans la mémoire. Par opposition, les types de base sont passés par valeur : on ne récupère que la valeur de la variable.
-
Pour ne pas modifier la liste passée en paramètre, on se dit que l'on peut en faire en copie et modifier cette copie dans la fonction. On propose la fonction suivante :
Code
def ajoute2(l, elt): ls = l ls.append(elt) return ls
Tester cette fonction avec
l1
(attention, non-modifiée !). Que contientl2
après l'appell2 = ajoute(l1, 4)
? Qu'en concluez-vous ? -
L'opération d'affectation ne crée pas une nouvelle liste, c'est toujours la même qui est manipulée. Il existe différentes solution pour créer une nouvelle liste avec les mêmes valeurs que la première. Tester les fonctions suivantes pour vérifier qu'elles remplissent bien l'objectif de ne pas modifier
l
:Code
def ajoute3(l, elt): ls = l.copy() ls.append(elt) return ls def ajoute3(l, elt): ls = list(l) ls.append(elt) return ls
-
Que faut-il donc faire pour éviter les effets de bord lorsque l'on manipule des listes dans des fonctions ?
Exercice 4 :¶
A faire sur ordinateur.
Les codes suivant correspondent à des algorithmes vus en première.
- Ecrire chacun sous la forme d'une fonction, dont on aura identifié les paramètres d'entrée et de sortie et dont on choisira le nom de manière adéquate. On veut des fonctions pures, n'utilisant pas de variables globales.
- Renommer les variables pour que l'on comprenne plus facilement leur rôle.
- Ajouter les spécifications des entrées et des sorties.
1.
Code
x = 0
y = 0
l = [6,8,7,1,0]
for i in range(len(l)):
if l[i]>x:
x = l[i]
y = i
print(x, y)
2.
Code
o = 0
t = [6,2,4,8,2,1,3]
for e in t:
if e == 2:
o = o+1
print(o)
TP : Programmation fonctionnelle¶
Fichier de correction : Correction
Vous devez garder un compte-rendu de chaque TP : sauvegardez votre travail dans un ou plusieurs fichier(s) .py.
A. Les fonctions d'ordre supérieur¶
Les fonctions d'ordre supérieur sont des éléments caractéristiques de la programmation fonctionnelle : des fonctions qui prennent en entrée d'autres fonctions, ou bien qui retournent des fonctions en sortie.
A.1. La fonction map
¶
La fonction map
prend comme arguments une fonction et une liste d'arguments à appliquer à cette fonction. Elle permet de ne pas avoir à réécrire un appel de fonction plusieurs fois, lorsque l'on veut appliquer le même traitement à différentes données.
- Exécuter le code suivant et regarder la valeur de
liste_int
:
Code
liste_str = ["000","147","1369874"]
liste_int = list(map(int, liste_str))
- On applique la fonction
int
aux différents éléments de la liste de chaînes de caractères pour les convertir. -
La fonction
map
renvoie un itérateur, c'est pourquoi pour avoir la liste des résultats, on a appliqué la fonctionlist
à cet itérateur. -
En s'inspirant de l'exemple précédent, utiliser la fonction map pour convertir en
str
une liste d'entiers (vous choisirez un exemple). -
Ecrire un code en paradigme impératif permettant de faire le même traitement qu'en 1. (comme si vous ne connaissiez pas la fonction
map
).
A.2. La fonction reduce
¶
La fonction reduce
de la bibliothèque functools
sert à calculer une valeur à partir d'une séquence de valeurs. Elle va appliquer une fonction passée en argument à un premier couple de valeurs de la séquence, puis à un second couple, etc jusqu'à avoir utilisé tous les éléments de la séquence.
- Importer la fonction depuis la bibliothèque :
Code
from functools import reduce
- Consulter sa documentation en tapant dans la console :
Code
>>> help(reduce)
-
Ecrire une fonction
minimum
renvoyant le minimum entre deux nombres passés en paramètre. -
Utiliser les informations que vous avez sur
reduce
pour appliquer la fonction sur la fonctionminimum
définie en 3., et une liste d'entiers. On doit obtenir en sortie le minimum de la liste des entiers. -
Ecrire un autre programme calculant le minimum d'une liste d'entiers, mais sans utiliser
reduce
.
B. Les fonctions anonymes, ou lambda
¶
Les fonctions d'ordre supérieur peuvent prendre en paramètre des fonctions déjà définies, ou bien il est possible de créer une nouvelle fonction "sur le vif", sans la nommer. Ce sont des fonctions anonymes, déclarées avec le mot-clé lambda
. La syntaxe est la suivante :
Mot-clé | Paramètres d'entrée | Valeur(s) de sortie |
---|---|---|
lambda | x, y : | (x+y,y) |
C'est utile lorsque l'on ne réutilisera pas la fonction ensuite, qu'on a besoin d'utiliser une fonction rapidement.
Exécuter le code suivant et identifier ce qui est stocké dans la variable s
:
Code
s = reduce(lambda x, y: x+y, [1,2,3,4,5,6,7,8,9])
Conclusion¶
Quels sont les intérêts d'utiliser le paradigme fonctionnel plutôt que le paradigme impératif ?