Photo: Chris RiedDans cet article, vous comprendrez ce qu'est un paradigme fonctionnel et comment utiliser la programmation fonctionnelle en Python. Vous en apprendrez également sur l'
abstraction de liste et d'autres compréhensions de liste.
Paradigme fonctionnel
Dans un paradigme impératif, vous écrivez un programme en spécifiant une séquence d'actions qui seront effectuées ultérieurement. A ce moment, les états (
approximativement Translator: variables, tableaux, etc. ) changent. Par exemple, laissez la variable A stocker la valeur 5, plus tard vous modifiez la valeur de cette variable. Vous utilisez des variables pour que leurs valeurs changent.
Dans un paradigme fonctionnel, vous ne dites pas à l'ordinateur quoi faire, mais spécifiez plutôt la nature des actions elles-mêmes. Quel est le plus grand commun diviseur d'un nombre, le résultat des calculs de 1 à n, etc.
Par conséquent, les variables ne changent pas. Une fois qu'une variable est initialisée, sa valeur est sauvegardée pour toujours (notez que dans les langages fonctionnels purs, ils ne sont même pas appelés variables). Par conséquent, dans le paradigme fonctionnel, les fonctions n'ont pas d'
effets secondaires . Un effet secondaire peut être défini comme le moment pendant lequel une fonction change quelque chose au-delà de ses limites. Jetez un oeil à un exemple:
a = 3 def some_func(): global a a = 5 some_func() print(a)
Le résultat de l'exécution de ce code est 5. En programmation fonctionnelle, le changement de variables est interdit, ainsi que le changement de fonctions de quelque chose au-delà de leurs frontières. Tout ce que la fonction peut faire est de calculer / traiter quelque chose et de retourner le résultat.
Maintenant, vous pourriez penser: «Pas de variables, pas d'effets secondaires? Pourquoi est-ce bien? " Vraiment une bonne question.
Si une fonction a été appelée deux fois avec les mêmes paramètres, elle retournera évidemment le même résultat. Si vous avez étudié quelque chose sur
les fonctions mathématiques , vous apprécierez cette opportunité. C'est ce qu'on appelle la transparence des liens ou la transparence référentielle. Comme les fonctions n'ont pas d'effets secondaires, si vous développez un programme de calcul, vous pouvez accélérer le processus d'exécution. Si le programme sait que func (2) vaut 3, nous pouvons nous en souvenir. Cela empêche la fonction d'être appelée à nouveau lorsque nous connaissons déjà le résultat.
Habituellement, dans la programmation fonctionnelle, les boucles ne sont pas utilisées. La récursivité est utilisée. La récursivité est un concept mathématique, en fait, cela signifie «se nourrir quelque chose». Dans une fonction récursive, la fonction elle-même s'appelle le rôle d'une sous-fonction. Voici un exemple de fonction récursive en Python:
def factorial_recursive(n):
Certains langages de programmation sont
paresseux . Cela signifie qu'ils calculent tout au dernier moment. Supposons que si le code doit exécuter 2 + 2, le programme fonctionnel calculera le résultat uniquement lorsque le résultat est nécessaire. Nous découvrirons la paresse de Python un peu plus tard.
La carte
Pour comprendre la carte, vous devez d'abord gérer les conteneurs itérables. Ceci est un tel conteneur sur lequel vous pouvez "passer". Ce sont souvent des listes ou des tableaux, mais il existe de nombreux conteneurs de ce type en Python. Vous pouvez même créer votre propre conteneur en introduisant
des méthodes magiques . Ces méthodes, comme les API, aident les objets à devenir plus pythoniques. Il existe 2 méthodes de ce type pour rendre un objet itérable:
class Counter: def __init__(self, low, high):
La première méthode magique est "___iter__" ou dunder (double souligné par des soulignements) iter renvoie un objet itérable, il est souvent utilisé au début d'une boucle. Dunder next (__next__) renvoie l'objet suivant.
Vérifiez ceci:
for c in Counter(3, 8): print(c)
Résultat d'exécution:
3
4
5
6
7
8
En Python, un itérateur est un objet qui n'a que la méthode __iter__. Cela signifie que vous pouvez accéder à l'emplacement des cellules de l'objet (conteneur), mais que vous ne pouvez pas "les parcourir". Certains objets n'ont que la merveilleuse méthode __next__, sans la méthode magique __iter__, par exemple, définie (plus à ce sujet plus tard). Dans cet article, nous couvrirons tout ce qui concerne les objets itérables.
Maintenant que nous savons ce qu'est un objet itérable, revenons à la fonction map. Cette fonction nous permet d'appliquer l'action d'une autre fonction à chaque élément d'un conteneur itéré. Nous voulons appliquer une fonction à chaque élément de la liste, cela est possible pour presque tous les conteneurs itérables. Map, prend deux arguments: la fonction à appliquer et le conteneur (liste, etc.).
map(function, iterable)
Supposons que nous ayons une liste avec les éléments suivants:
[1, 2, 3, 4, 5]
Et nous voulons cadrer chaque élément, cela peut être fait comme ceci:
x = [1, 2, 3, 4, 5] def square(num): return num*num print(list(map(square, x)))
Les fonctions fonctionnelles en Python sont paresseuses. Si nous n'ajoutons pas «list ()», la fonction stockera la description du conteneur (list), et non la liste elle-même. Nous devons directement dire à Python de le convertir en liste.
C'est un peu étrange de passer d'une définition non paresseuse à une définition paresseuse si soudainement. Vous vous y habituerez si vous pensez plus d'une manière fonctionnelle que d'un impératif.
L'écriture de fonctions, par exemple, «carré (num)» est normale, mais pas entièrement correcte. Avons-nous besoin de déclarer une fonction entière juste pour l'utiliser dans la carte? Cela peut être simplifié en introduisant des fonctions lambda (anonymes).
Expressions lambda
Les expressions lambda sont des fonctions sur une seule ligne, par exemple, voici une expression lambda qui met au carré le nombre résultant:
square = lambda x: x * x
Et exécutez ceci:
>>> square(3)
9
Je vous entends. "Brandon, où sont les arguments?" De quoi s'agit-il? Ce n'est pas comme une fonction. »
Oui, cela peut être déroutant, mais cela peut s'expliquer. Dans cette ligne, nous attribuons quelque chose à la variable «carré». Cette partie:
lambda x: x * x
Indique à Python que nous utilisons la fonction lambda, et l'entrée est nommée x. Tout ce qui se passera après les deux points est ce qui arrivera à l'entrée, et nous obtiendrons automatiquement le résultat plus tard.
Pour notre programme a pris la forme d'une ligne, vous devez le faire:
x = [1, 2, 3, 4, 5] print(list(map(lambda num: num * num, x)))
Ainsi, dans les expressions lambda, les arguments sont à gauche et les actions sur eux sont à droite. C'est un peu désordonné, personne ne le nie. La vérité est qu'il y a quelque chose dedans, pour écrire un tel code fonctionnel. De plus, il est très cool de convertir des fonctions en une seule ligne.
Réduire
Réduire est une fonction qui transforme un conteneur itérable en une seule chose. Autrement dit, des calculs sont effectués qui transforment la liste en un seul numéro. Cela ressemble à ceci:
reduce(function, list)
Nous pouvons (et utiliserons souvent) des fonctions lambda comme argument de fonction.
Si nous voulons multiplier tous les nombres de la liste, cela peut être fait comme ceci:
product = 1 x = [1, 2, 3, 4] for num in x: product = product * num
Et avec réduire, cela ressemblera à ceci:
from functools import reduce product = reduce((lambda x, y: x * y),[1, 2, 3, 4])
Le résultat sera le même, mais le code est plus court et avec la connaissance de la programmation fonctionnelle pour l'utiliser plus précisément.
Filtrer
La fonction de filtre prend un conteneur itérable et le filtre selon une règle donnée (également une fonction).
Habituellement, il prend une fonction et une liste en entrée. Plus tard, il applique une fonction à chaque élément de la liste, si la fonction renvoie True, rien ne se passe et si False, l'élément est supprimé de la liste.
Syntaxe:
filter(function, list)
Regardons un exemple sans utiliser de filtre:
x = range(-5, 5) new_list = [] for num in x: if num < 0: new_list.append(num)
Avec filtre:
x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x))
Fonctions d'ordre supérieur
Les fonctions d'ordre supérieur peuvent prendre des fonctions comme arguments et les renvoyer. Un exemple simple ressemblerait à ceci:
def summation(nums): return sum(nums) def action(func, numbers): return func(numbers) print(action(summation, [1, 2, 3]))
Ou un exemple est encore plus simple:
def rtnBrandon(): return "brandon" def rtnJohn(): return "john" def rtnPerson(): age = int(input("What's your age?")) if age == 21: return rtnBrandon() else: return rtnJohn()
Rappelez-vous plus tôt, j'ai dit que la vraie programmation fonctionnelle n'utilisait pas de variables. Les fonctions d'ordre supérieur rendent cela possible. Vous n'avez pas besoin de sauvegarder la variable quelque part si vous passez des informations à travers un long «tunnel» de fonctions.
Toutes les fonctions en Python sont des objets de première classe. Un objet de la première classe est défini comme tel, ce qui correspond à un ou plusieurs des paramètres suivants:
- Crée un cycle de service
- Attribué à une variable ou un élément dans une structure de données
- Passé comme argument de fonction
- Renvoyé suite à l'exécution de la fonction
Ainsi, toutes les fonctions en Python sont des objets de première classe et peuvent être utilisées comme des fonctions d'ordre supérieur.
Application partielle
Une utilisation partielle (également interruption) est un peu étrange, mais très cool. Vous pouvez appeler la fonction sans utiliser tous les arguments donnés. Regardons un exemple. Nous voulons créer une fonction qui prend 2 arguments, la base et le degré, et renvoie la base élevée à la puissance, cela ressemble à ceci:
def power(base, exponent): return base ** exponent
Maintenant, nous devons créer une fonction distincte pour la quadrature et la calculer à l'aide de la fonction de puissance:
def square(base): return power(base, 2)
Cela fonctionne, mais que faire si nous voulons découper un nombre en cubes? Ou au 4e degré? Devriez-vous écrire ces fonctions pour toujours? Bien sûr que vous le pouvez. Mais les programmeurs sont paresseux. Si vous répétez la même chose plusieurs fois, il existe probablement un moyen de le faire plus rapidement et d'arrêter de faire des répétitions. Une application partielle peut être utilisée ici. Regardons un exemple de la fonction de puissance utilisant une application partielle:
from functools import partial square = partial(power, exponent=2) print(square(2))
N'est-ce pas cool? Nous pouvons appeler une fonction qui a besoin de 2 arguments, en utilisant seulement 1, et en spécifiant ce que le second sera seul.
Vous pouvez également utiliser une boucle pour simuler une fonction de puissance qui fonctionnera avec des cubes jusqu'à la 1000e puissance.
from functools import partial powers = [] for x in range(2, 1001): powers.append(partial(power, exponent = x)) print(powers[0](3))
La programmation fonctionnelle ne correspond pas aux canons pythoniques
Vous avez peut-être remarqué que beaucoup de choses que nous voulons faire dans la programmation fonctionnelle tournent autour des listes. Outre la fonction de réduction et l'application partielle, toutes les fonctions que vous avez vues génèrent des listes. Guido (le créateur de Python`a) n'aime pas les choses fonctionnelles en Python`e, car Python a sa propre méthode de création de listes.
Si vous écrivez «importer ceci» dans la console, vous obtiendrez:
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one — and preferably only one — obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let's do more of those!
C'est Python Zen. Ceci est un verset sur ce que signifie être pythoniste. La partie qui nous intéresse est:
Il devrait y avoir une - et de préférence une seule - manière évidente de le faire.
Il ne devrait y avoir qu'une seule - et de préférence une seule - façon évidente de faire quelque chose.
En Python, la carte et le filtre peuvent faire la même chose que l'abstraction de liste (
lien ). Cela viole l'une des règles de Python-Zen, donc cette partie de la programmation fonctionnelle n'est pas "pythonique".
Les prochaines choses à parler sont la fonction lambda. En Python, une fonction lambda est une fonction normale. Et en fait, c'est du sucre syntaxique. Ces deux parties font la même chose:
foo = lambda a: 2 def foo(a): return 2
Une fonction standard peut toujours être la même qu'une fonction lambda, mais pas l'inverse. Une fonction Lambda ne peut pas être la même qu'une fonction normale.
C'était une petite remarque sur la raison pour laquelle la programmation fonctionnelle ne correspond pas tout à fait à l'idéologie pythonique. Plus tôt, j'ai mentionné l'abstraction des listes (
également l'inclusion des listes), maintenant parlons-en.
Abstraction de liste
J'ai déjà dit que tout ce qui peut être fait en utilisant la carte et le filtre peut être fait en utilisant l'abstraction de liste. Dans cette partie, nous en discuterons.
L'abstraction de listes est un moyen de créer des listes en Python. Syntaxe:
[function for item in iterable]
Carré chaque élément de la liste, par exemple:
print([x * x for x in [1, 2, 3, 4]])
D'accord, nous pouvons voir comment appliquer la fonction à chaque élément de la liste. Comment contourner le filtre? Jetez un oeil à ce code:
x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x)) print(all_less_than_zero)
Utilisez maintenant l'abstraction de liste:
x = range(-5, 5) all_less_than_zero = [num for num in x if num < 0]
L'abstraction de liste prend en charge les expressions conditionnelles de ce type. Vous n'avez plus besoin d'utiliser un million de fonctions pour obtenir quelque chose. En fait, si vous essayez de faire quelque chose avec une liste, il est possible qu'elle soit plus propre et plus facile à réaliser avec l'abstraction des listes.
Et si nous voulons mettre au carré chaque élément de la liste qui est en dessous de zéro. Avec la fonction lambda, la carte et le filtre, cela ressemblera à ceci:
x = range(-5, 5) all_less_than_zero = list(map(lambda num: num * num, list(filter(lambda num: num < 0, x))))
Cette entrée n'est pas rationnelle et pas très simple. En utilisant l'abstraction de liste, cela ressemblera à ceci:
x = range(-5, 5) all_less_than_zero = [num * num for num in x if num < 0]
L'abstraction des listes n'est bonne, curieusement, que pour les listes. La carte et le filtre fonctionnent pour chaque conteneur itérable, alors qu'est-ce qui ne va pas? .. Oui, vous pouvez utiliser l'abstraction pour chaque conteneur itérable que vous rencontrez.
Autres abstractions
Vous pouvez appliquer l'abstraction pour chaque conteneur itérable.
Chaque conteneur itérable peut être créé en utilisant l'abstraction. À partir de la version 2.7, vous pouvez même créer un dictionnaire (table de hachage).
Si quelque chose est un conteneur itérable, alors quelque chose peut être généré. Regardons le dernier exemple en utilisant set. Si vous ne savez pas de quel ensemble il s'agit, consultez également
cet article écrit par moi. En bref:
- Set est un conteneur d'éléments; les éléments qu'il contient ne sont pas répétés
- L'ordre n'est pas important
Comme vous l'avez peut-être remarqué, l'ensemble, comme un dictionnaire, utilise des accolades. Python est vraiment intelligent. Il devinera si vous utilisez une abstraction de dictionnaire ou une abstraction set`a, selon que vous spécifiez ou non des paramètres supplémentaires pour le dictionnaire. Si vous voulez en savoir plus sur les abstractions, lisez
ceci . S'il s'agit d'abstractions et de génération, alors
celle-ci .
Résumé
La programmation fonctionnelle est excellente. Le code fonctionnel peut être propre ou pas très. Certains pythonistes inconditionnels n'acceptent pas le paradigme fonctionnel de Python. Vous devez utiliser ce que vous voulez et ce qui vous convient.
Page d'auteur .