Programmation fonctionnelle en Python
adaptation libre de la documentation officielle
Table des matières
Programmation fonctionnelle en Python.........................................................................1
Introduction........................................................................................................................1
Preuve formelle.......................................................................................................3
Modularité................................................................................................................3
Facilité de débogage et de test...............................................................................3
Composabilité..........................................................................................................4
Itérateurs............................................................................................................................4
Les types de données qui supportent les itérateurs...............................................6
Expressions génératrices et compréhension des listes.................................................7
Générateurs........................................................................................................................9
Passer des valeurs à un générateur.....................................................................11
Fonctions prédéfinies......................................................................................................12
Le module itertools..........................................................................................................14
Créer de nouveaux itérateurs...............................................................................14
Appels de fonctions sur des éléments..................................................................16
Sélectionner des éléments....................................................................................16
Grouper des éléments...........................................................................................17
Le module functools........................................................................................................17
Le module operator...............................................................................................19
Petites fonctions et expression lambda.........................................................................19
Dans ce document, nous ferons une excursion dans les données de Python qui conviennent à
l'implémentation de programmes dans le style fonctionnel. Après une introduction aux
concepts de la programmation fonctionnelle, nous jetterons un œil sur les données telles les
itérateurs (iterators) et les générateurs (generators) et les modules qui en relèvent tels que
itertools et functools.
Introduction
Cette section explique les concepts de base de la programmation fonctionnelle : si vous vous
intéressez uniquement aux caractéristiques du langage Python, passez à la section suivante.
Les langages de programmation réalisent la décomposition des problèmes de plusieurs
façons :
* la plupart des langages sont procéduraux : les programmes sont des listes
d'instructions qui disent à la machine ce qu'il faut faire des données d'entrées fournies
du programmes. C, Pascal, et même le shell d'Unix sont des langages procéduraux.
* dans les langages déclaratifs, vous écrivez une spécification qui décrit le problème à
résoudre, et le langage implémente les figures sur la façon de réaliser efficacement le
calcul. SQL est le langage déclaratif qui vous est sûrement le plus familier ; une
requête SQL décrit les données que vous désirez retrouver et la machine SQL décide
adaptation de la doc Python page 1 Programmation fonctionnelle en Python
quelles tables ou quels index elle examine, quelles clauses partielles doivent être
calculées en priorité etc.
* la programmation orientée objets manipule des collections d'objets. Les objets ont
un état interne et supportent des méthodes qui requièrent ou modifient l'état interne
comme il le faut. Smalltalk et Java sont des langages orientés objets. C++ et le langage
Python supportent la programmation orientée objets mais ne forcent pas à utiliser les
caractéristiques de l'orienté objets.
* la programmation fonctionnelle décompose un problème en un ensemble de
fonctions. Dans l'idéal, les fonctions prennent des entrées, et produisent des sorties, et
n'ont aucun état interne qui affecte les sorties produites pour des données d'entrées
données. Parmi les plus connus des langages fonctionnelle, citons la famille ML
(Standard ML, Ocaml, et d'autres variantes) et Haskell.
NDT : Haskell est un langage destiné à la recherche sur la programmation. C'est un langage
fonctionnel pur, ce qui signifie que par défaut aucun effet de bord n'est autorisé, ni les
entrées/sorties, ni même l'affectation d'une variable. Haskell impose ce style dans tout code
qui ne signale pas explicitement par son type qu'il contient des effets de bord.
Les concepteurs de certains langages informatiques choisissent de mettre l'accent sur une
approche particulière de programmer. Cela peut rendre difficile d'écrire des programmes qui
utilisent une approche différente. D'autres langages sont des langages multi-paradigmes qui
supportent plusieurs approches différentes. Lisp, C++, Python sont multi-paradigmes. Vous
pouvez écrire des programmes ou des librairies qui sont largement procéduraux, ou orientés
objets, ou fonctionnels avec tous ces langages. Dans un grand programme, des sections
différentes peuvent être écrites selon des approches différentes ; par exemple, les unités
d'interface graphique peuvent être orientés objet alors que la logique du calcul est procédurale
ou fonctionnelle.
Dans un programme fonctionnel, l'entrée remonte à travers un ensemble de fonctions. Chaque
fonction opère sur son entrée et produit une nouvelle entrée. Le style fonctionnel décourage
les fonctions qui on un effet de bord qui modifie l'état interne ou réalise d'autres changements
qui ne sont pas visibles dans la valeur retournée par la fonction. Les fonctions sans effet de
bord sont dites purement fonctionnelles. Éviter les effets de bord, cela signifie qu'on ne peut
utiliser de structures de données qui sont mises à jour quand le programme tourne ; chaque
sortie d'une fonction doit uniquement dépendre de ses entrées.
Certains langages sont très stricts sur la pureté et ne possèdent pas l'instruction d'affectation
comme a=3 ou c=a+b, mais il difficile d'éviter tous les effets de bord. Afficher à l'écran ou
écrire un fichier sur un disque sont des effets de bord. Par exemple en Python, l'appel des
fonctions print() ou time.sleep() ne retourne rien de bien utile ; ces fonctions sont
uniquement appelées pour leur effet de bord qui est d'envoyer du texte à l'écran ou d'arrêter
l'exécution durant une seconde.
Les programmes Python écrits dans un style fonctionnel ne tombent pas dans ces extrêmes
que sont l'évitement de toute entrée/sortie ou de toute affectation ; ainsi, ils auront une
interface d'apparence fonctionnelle, mais utiliseront des caractères non fonctionnels en
interne. Par exemple, l'implémentation d'une fonction va utiliser l'affectation à des variables
locales, mais évitera de modifier des variables globales et autres effets de bord.
On peut considérer la programmation fonctionnelle comme l'exact opposé de la
programmation orientée objets. Les objets sont un peu comme des capsules qui contiennent
des états internes avec une collection de méthodes qui quand vous les appelez, modifient ces
états, et un programme consiste à réaliser le bon ensemble de ces changement d'états. La
programmation fonctionnelle désire éviter les changements d'état autant que possible et
adaptation de la doc Python page 2 Programmation fonctionnelle en Python
procède par transmission de données entre des fonctions. Python permet de combiner les deux
approches en écrivant des fonctions qui acceptent et retournent des instances qui représentent
les objets de votre application (e-mail, transactions, etc).
La conception fonctionnelle peut apparaître comme une étrange contrainte dans le travail.
Pourquoi éviter les objets et les effets de bord ? Il y a des avantages théoriques et pratiques à
adopter le style fonctionnel :
* la preuve formelle
* la modularité
* la composabilité
* la facilité de débogage et de test.
Preuve formelle
Un bénéfice théorique : il est facile de construire une preuve mathématique qu'un
programme fonctionnel est correct.
Pendant longtemps, les chercheurs se sont consacré à trouver les moyens de prouver la
correction d'un programme. Ceci est différent de tester un programme avec des entrées
suffisamment nombreuses pour conclure que les sorties sont normalement correctes, ou de lire
le code source d'un programme et d'en déduire que le code paraît bon ; le but est au contraire
de fournir une preuve rigoureuse que le programme produit le bon résultat pour toutes les
entrées possibles.
La technique utilisée pour prouver la correction d'un programme est d'exhiber les invariants,
propriété des données entrées et des variables du programme qui sont toujours vraies. Pour
chaque ligne de code, on peut montrer que si les invariants X et Y sont vrais avant que la ligne
soit exécutée, les invariants légèrement différents X' et Y' sont vrais après que la ligne soit
exécutée. On continue le procédé jusqu'à atteindre la fin du programme, et à ce niveau, les
invariants doivent vérifier les conditions désirées sur la sortie du programme.
L'évitement des affectations en programmation fonctionnelle provient du fait que les
affectations sont difficiles à manier avec la technique de preuve ; les affectations peuvent
casser les invariants qui étaient vrais avant l'affection en ne produisant pas un nouvel invariant
qui peut se propager vers la suite.
Malheureusement, prouver qu'un programme est correct est largement impraticable et ne
relève pas de la programmation en Python. Même les programmes triviaux requièrent ds
preuves qui remplissent plusieurs pages ; la preuve de correction pour un programme un peu
compliqué serait énorme, et peu de programmes, voire aucun ne le fait aujourd'hui ;
l'interpréteur Python, l'analyseur XML ou votre navigateur devraient être prouvés. Même si
vous écrivez ou engendrez une preuve, vous vous poserez le question de la vérification de la
preuve ; peut-être contient-elle une erreur, et vous croirez vraiment que votre programme est
correct.
Modularité
Un bénéfice plus pratique de la programmation fonctionnelle est qu'elle contraint à découper
votre problème en petits morceaux. Il en résulte des programme plus modulaires. Il est facile
de spécifier et d'écrire une petite fonction qui fait une seule chose plutôt qu'une grande
fonction qui fait des transformations complexes. Les petites fonctions sont aussi plus facile à
lires et à vérifier.
Facilité de débogage et de test
Tester et déboguer un programme en style fonctionnel es plus facile.
adaptation de la doc Python page 3 Programmation fonctionnelle en Python
Le débogage est simplifié parce que les fonctions sont en général petites et clairement
spécifiées. Quand un programme ne fonctionne pas, chacune des fonctions est un point
d'interface où l'on peut vérifier que les données sont correctes. Vous pouvez voir les entrées et
sorties intermédiaires et rapidement isoles la fonction responsable du bug.
Il est facile de tester parce que chaque fonction est un sujet potentiel pour un test unitaire. Les
fonctions ne dépendent pas d'un état du système qui a besoin d'être reprroduit avant de lancer
un test ; à la place vous avez seulement à synthétiser les bonnes entrées et à vérifier que les
sorties correspondent aux attentes.
Composabilité
Quand vous travaillez sur un programme de style fonctionnel, vous allez écrire plusieurs
fonctions avec des entrées et sorties diverses. Certaines de ces fonctions seront inévitablement
spécialisées à une application particulière, mais d'autres pourront servir dans une grand
variété de programmes. Par exemple, une fonction qui prend un chemin de répertoire et
retourne les fichiers XML du répertoire, ou une fonction qui prend un nom de fichier et
retourne son contenu peuvent être appliquées dans de nombreuses situation différentes.
Avec le temps, vous constituerez une librairie personnelle d'utilitaires. Souvent vous
assemblerez de nouveaux programmes en agençant des fonctions existantes dans des
configurations nouvelles et vous écrirez des fonctions spécialisées pour le travail en cours.
Itérateurs
Je vais commencer en examinant une caractéristique Python qui est fondamentale pour écrire
des programmes de style fonctionnel : les itérateurs (iterators).
Un itérateur est un objet qui représente un flux de données ; cet objet fournit ses données un
élément à la fois. Un itérateur Python doit supporter la méthode appelée __next__() qui ne
prend aucun argument et retourne toujours l'élément qui suit du flux de données. S'il n'y a pas
d'élément suivant dans le flux, __next__() doit lever une exception StopIteration. Les
itérateurs ne sont pas nécessairement finis cependant ; il est parfaitement raisonnable d'écrire
un itérateur qui produit un flux infini de données.
La fonction prédéfinie iter() prend un objet quelconque et essaie d'en retourner un itérateur,
qui va retourner les contenus de l'objet ou éléments, produisant un TypeError si l'objet de
peut pas supporter l'itération. De nombreux types de données prédéfinis supportent l'itération,
les exemples les plus communs étant les listes et les dictionnaires. Un objet est appelé
itérable si vous pouvez construire un itérateur à partir de lui.
On peut expérimenter l'interface d'itération de façon manuelle :
>>> L = [1, 2, 3]
>>> it = iter (L)
>>> it
<list_iterator object at 0x7fbd29398dd0>
>>> it.__next__()
1
>>> it.__next__()
2
>>> it.__next__()
3
>>> it.__next__()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
it.__next__()
StopIteration
adaptation de la doc Python page 4 Programmation fonctionnelle en Python
>>> it = iter (L)
>>> next (it)
1
>>> next (it)
2
>>> next (it)
3
>>> next (it)
Traceback (most recent call last):
File "<Shelley#11>", line 1, in <module>
next (it)
StopIteration
Python utilise les objets itérables dans différentes contextes, le plus important étant le for,
dans l'instruction for X in in Y , ou Y doit être un itérateur ou un objets sur lequel iter()
peut créer un itérateur. On alors l'équivalence :
>>> obj = (1,2,3,4)
>>> for item in obj :
print (item, end = " ")
1 2 3 4
>>> for item in iter (obj) :
print (item, end = " ")
1 2 3 4
Les itérateurs peuvent être matérialisés comme liste ou tuples en utilisant les constructeurs
list() ou tuple().
>>> L = [1, 2, 3]
>>> iterateur = iter(L)
>>> iterateur
<list_iterator object at 0x7fbd29398dd0>
>>> t = tuple (iterateur)
>>> t
(1, 2, 3)
Les séquences déballées peuvent aussi supporter les itérateurs : si vous connaissez un itérateur
qui retourne N éléments, vous pouvez le déballer dans un tuple à N éléments :
>>> L = [1, 2, 3]
>>> iterateur = iter (L)
>>> a, b, c = iterateur
>>> a, b, c
(1, 2, 3)
NDT : a, b, c = qqch construit le tuple (a, b, c) ; c'est l'emballage des tuples
Les fonctions prédéfinies comme max() ou min() peuvent comme argument un simple
itérateur et retourner le plus grand ou le plus petit élément. Les opérateurs in et not in
supportent aussi les itérateurs : X in itérateur est vrai si X est trouvé dans le flux retourné
par l'itérateur. Vous ne pourrez pas les utiliser dans les question utilisant des itérateurs infinis ;
max() et min() ne retournent jamais, et si l'élément X n'apparaît pas dans le flux, les
adaptation de la doc Python page 5 Programmation fonctionnelle en Python
1 / 20 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !