Python >> Tutoriel Python >  >> Python

Mapper, filtrer, réduire - Travailler sur des flux en Python

Savez-vous comment utiliser les flux Python comme les flux Java ?

Un flux est une séquence d'éléments. Avec map() , filter() , et reduce() – les trois fonctions fondamentales de la programmation fonctionnelle – vous pouvez opérer sur une séquence d'éléments. Dans cet article, nous allons apprendre à travailler avec des flux en Python comme nous les utilisons en Java.

Mais d'abord, disons un mot sur la programmation fonctionnelle.

Qu'est-ce que la programmation fonctionnelle ?

La programmation fonctionnelle est un paradigme de programmation qui décompose un problème en fonctions individuelles. Chaque fonction, si possible, prend un ensemble d'arguments d'entrée et produit une sortie. Dans ce paradigme, nous évitons autant que possible les types de données modifiables et les changements d'état.

Il met également l'accent sur la récursivité plutôt que sur les boucles, en se concentrant sur les listes, les fonctions pures et les fonctions d'ordre supérieur.

Dans cet article, nous allons explorer map() , filter() , et reduce() en Python. Ce sont les méthodes Python utilisées pour effectuer les opérations de mappage, de filtrage et de réduction qui sont fondamentales dans la programmation fonctionnelle.

Notons tout d'abord que map() , filter() , et reduce() sont écrits en C et sont hautement optimisés en termes de vitesse et d'utilisation de la mémoire, ce qui en fait un meilleur choix que le Python standard for boucle.

Comme prérequis, il est indispensable d'avoir quelques connaissances sur les fonctions en Python. Si vous avez besoin d'un rappel, consultez l'article Comment définir une fonction en Python.

Travailler sur les flux en Python :map()

map() prend une fonction et un ou plusieurs itérables comme arguments. La sortie est un itérateur qui renvoie les éléments transformés.

Voici la syntaxe :

map(function, iterable[, iterable1, iterable2,..., iterableN])

Ce premier argument de map() est une fonction de transformation, où chaque élément d'origine est transformé en un nouvel élément. Il peut s'agir de n'importe quel Python appelable.

Supposons que vous deviez prendre une liste de valeurs numériques et la transformer en une liste contenant la valeur cubique de chaque nombre de la liste d'origine. Vous pouvez utiliser un for bouclez et codez quelque chose comme ceci :

>>> # Define numbers to transform and an empty cube list
>>> num = [2, 3, 6, 9, 10]
>>> cube = []

>>> # Define for loop to transform the numbers
>>> for n in num:
...     cube.append(n ** 3)

>>> # Compute cube of num
>>> cube
[8, 27, 216, 729, 1000]

Cette boucle renvoie une liste de valeurs de cube. Le for loop itère sur num et applique une transformation de cube sur chaque valeur. Enfin, il stocke les valeurs résultantes dans cube .

map() peut obtenir le même résultat sans boucle for :

>>> # Define the transformation function
>>> def cube(num):
...   return num ** 3

>>> # List of numbers to transform
>>> num = [2, 3, 6, 9, 10]

>>> # Call map function to apply cube on each number
>>> cubed = map(cube, num)

>>> # Create a list containing the cubed values
>>> list(cubed)
[8, 27, 216, 729, 1000]

L'exemple ci-dessus illustre comment transformer une liste de valeurs avec map() et une fonction définie par l'utilisateur.

Tout type de Python appelable fonctionne avec map() telles que les classes, les méthodes d'instance, les méthodes de classe, les méthodes statiques et les fonctions.

Un modèle typique lors de l'utilisation de map() est d'utiliser une fonction Python lambda comme premier argument. Les fonctions Lambda sont un moyen pratique de transmettre une fonction basée sur une expression à map() . Pour illustrer cela, nous pouvons réutiliser l'exemple des valeurs de cube à l'aide d'une fonction Python lambda :

>>> # List of input numbers to transform
>>> num = [2, 3, 6, 9, 10]

>>> # Define a lambda function to iterate on each value of num.
>>> cubed = map(lambda n: n ** 3, num)

>>> # Create a list containing the cubed values
>>> list(cubed)
[8, 27, 216, 729, 1000]

Si vous saisissez plusieurs itérables jusqu'à map() , alors la fonction de transformation doit prendre autant d'arguments que le nombre d'itérables que vous transmettez. Chaque itération transmettra une valeur de chaque itérable comme argument à la fonction.

Lorsque plusieurs itérables sont passés, map() regroupera les éléments à travers les itérables. Par exemple, il prendra chaque premier élément et le passera à la fonction.

Cette technique est utile pour fusionner deux ou plusieurs itérables de valeurs numériques qui utilisent différentes opérations mathématiques. Voici quelques exemples qui utilisent les fonctions Python lambda pour calculer diverses opérations mathématiques sur plusieurs itérables d'entrée :

>>> list(map(lambda x, y: x / y, [6, 3, 5], [2, 4, 6]))
[3.0, 0.75, 0.8333333333333334]

>>> list(map(lambda x, y, z: x * y + z, [6, 2], [7, 3], [8, 10]))
[50, 16]

Dans le premier exemple, nous utilisons une opération de division pour fusionner deux itérables de trois éléments chacun. Dans le deuxième exemple, nous multiplions et additionnons les valeurs de trois itérables comme 6 x 7 + 8 =50 et 2 x 3 + 10 =16.

Aussi, map() est utile pour traiter et transformer des itérables de valeurs numériques ; de nombreuses transformations liées aux mathématiques peuvent être effectuées avec map() .

Nous devrions également mentionner starmap(), qui est très similaire à map() . Selon la documentation Python, starmap() est utilisé à la place de map() lorsque les paramètres d'argument sont déjà regroupés en tuples à partir d'un seul itérable, ce qui signifie que les données ont été "pré-compressées".

Pour appeler le starmap() , nous devons importer itertools . Exécutons un exemple rapide :

>>> import itertools

>>> # Define a list of tuples
>>> num = [(2, 3), (6, 9), (10,12)]

>>> # Define a lambda function to a list of tuples
>>> multiply = itertools.starmap(lambda x,y: x * y, num)

>>> # Create a list containing the multiplied values
>>> list(multiply)
[6, 54, 120]

Travailler sur les flux en Python :filter()

Une opération de filtrage traite un itérable et extrait les éléments qui satisfont une opération donnée. Cela peut être effectué avec la fonction intégrée filter() de Python.

La syntaxe de base est :

filter(function, iterable)

Les fonctions de filtrage peuvent filtrer les valeurs indésirables et conserver les valeurs souhaitées dans la sortie. Le function argument doit être une fonction à argument unique. Il s'agit généralement d'une fonction booléenne qui renvoie soit True ou False .

Le iterable L'argument peut être n'importe quel itérable Python, tel qu'une liste, un tuple ou un ensemble. Il peut également contenir des objets générateur et itérateur. Notez que filter() n'accepte qu'un seul itérable.

filter() est souvent utilisé avec une fonction Python lambda comme autre moyen de définir une fonction définie par l'utilisateur. Exécutons un exemple dans lequel nous voulons obtenir uniquement les nombres pairs d'une liste :

>>> # List of numbers
>>> num = [12, 37, 34, 26, 9, 250, 451, 3, 10]
  
>>> # Define lambda function to filter even numbers
>>> even = list(filter(lambda x: (x % 2 == 0), num)) 
  
>>> # Print the even numbers
>>> print(even) 
[12, 34, 26, 250, 10]

L'exemple ci-dessus utilise filter() pour vérifier si les nombres sont pairs. Si cette condition est remplie et renvoie True, le nombre pair "passe par le filtre".

Notez qu'il est possible de remplacer filter() avec une liste en compréhension :

# Generate a list with filter()
list(filter(function, iterable))

# Generate a list with a list comprehension
[i for i in iterable if function(i)]

Dans les deux cas, le but est de retourner un objet liste.

Lors de la manipulation de listes en Python, l'approche de compréhension de liste est plus explicite que filter() . Cependant, les compréhensions de liste manquent d'évaluation paresseuse. Aussi, en lisant le code, on sait immédiatement que filter() effectue une opération de filtrage. En ce sens, les compréhensions de liste ne sont pas aussi explicites.

Utiliser groupby() et sort() en Python

Dans cette partie, nous aborderons d'autres outils pour travailler sur les flux en Python :sort() et groupby()

Le sort() est un outil utile pour manipuler des listes en Python. Par exemple, si vous devez trier une liste par ordre croissant ou inverse, vous pouvez utiliser ce qui suit :

>>> num = [24, 4, 13, 35, 28]

>>> # sort the list in ascending order
>>> num.sort()
>>> print(num)
[4, 13, 24, 28, 35]

Et par ordre décroissant :

>>> # sort the list in descending order
>>> numbers.sort(reverse=True)
>>> print(numbers)
[35, 28, 24, 13, 4]

Il est important de noter que le sort() La méthode mute la liste d'origine et il est donc impossible de ramener les éléments de la liste à leur position d'origine.

Ensuite, itertools.groupby() prend une liste d'itérables et les regroupe en fonction d'une clé spécifiée. La clé est utile pour spécifier quelle action doit être entreprise pour chaque iterable . La valeur de retour sera similaire à un dictionnaire, comme c'est le cas dans le {key:value } formulaire. De ce fait, il est très important de trier les items avec la même clé que celle utilisée pour le regroupement. Cela garantira la cohérence du code et évitera des résultats inattendus.

Exécutons un exemple dans lequel nous avons des dépenses mensuelles stockées sous forme de liste de tuples.

Nous voulons regrouper ces dépenses par mois et enfin calculer les dépenses mensuelles totales.

>>> import itertools

>>> # Create a list of monthly spendings as a list of tuples  
>>> spendings = [("January", 25), ("February", 47), ("March", 38), ("March", 54), ("April", 67), 
             ("January", 56), ("February", 32), ("May", 78), ("January", 54), ("April", 45)]

>>> # Create an empty dictionary to store the data
>>> spendings_dic = {}

>>> # Define a func variable to specify the grouping key
>>> func = lambda x: x[0]

>>> # Group monthly spendings by month in a dictionary 
>>> for key, group in groupby(sorted(spendings, key=func), func):
...     spendings_dic[key] = list(group) 

>>> spendings_dic
{'April': [('April', 67), ('April', 45)],
 'February': [('February', 47), ('February', 32)],
 'January': [('January', 25), ('January', 56), ('January', 54)],
 'March': [('March', 38), ('March', 54)],
 'May': [('May', 78)]}

Dans l'extrait ci-dessus, nous avons utilisé sorted() au lieu de sort() . C'est parce que nous voulions trier un itérable qui n'était pas encore une liste.

Contrairement à sort() , sorted() créera une copie de la liste d'origine, permettant de récupérer la commande d'origine. Parce que sorted() doit créer une copie de la liste d'origine, elle est plus lente que sort() . Si vous voulez en savoir plus sur le tri en Python, j'ai écrit un article qui explique différentes façons de définir vos propres critères de tri .

Enfin, nous pouvons utiliser map() de la section précédente pour additionner les dépenses mensuelles :

>>> # Apply map() to sum the monthly spendings
>>> monthly_spendings = {key: sum(map(lambda x: x[1], value)) for key, value in spendings_dic.items()}
>>> monthly_spendings
{'April': 112, 'February': 79, 'January': 135, 'March': 92, 'May': 78}

Pour en savoir plus sur l'application des expressions Python lambda, le filtrage des lignes et la sélection des colonnes dans un bloc de données Python avec Pandas, consultez l'excellent article de Yigit Aras sur le filtrage des lignes et la sélection des colonnes dans un bloc de données.

Travailler sur les flux en Python :reduce()

La fonction reduce() implémente une technique appelée pliage ou réduction. Il prend une fonction existante, l'applique de manière cumulative à tous les éléments de iterable et renvoie une seule valeur finale.

reduce() était à l'origine une fonction intégrée et devait être supprimée. Il a été déplacé vers functools.reduce() en Python 3.0 en raison de problèmes de performances et de lisibilité possibles.

Sauf si vous ne trouvez pas d'autre solution que reduce() , vous devez éviter de l'utiliser. Le reduce() La fonction peut créer des problèmes de performances abyssaux car elle appelle des fonctions plusieurs fois, ce qui rend votre code lent et inefficace.

Dans la mesure du possible, travaillez avec une fonction dédiée pour résoudre ces cas d'utilisation. Fonctions telles que sum() , any() , all() , min() , max() , len() , math.prod() sont plus rapides, plus lisibles et Pythonic. Ces fonctions sont également hautement optimisées et implémentées en C, ce qui les rend rapides et efficaces.

reduce() peut également compromettre la lisibilité de votre code lorsque vous l'utilisez avec des fonctions complexes définies par l'utilisateur ou des fonctions lambda. reduce() fonctionnera généralement mieux qu'un Python for boucle, mais comme l'a expliqué le créateur de Python Guido Van Rossum, une boucle Python est souvent plus facile à comprendre que reduce() . Il recommande que l'applicabilité de reduce() être limité aux opérateurs associatifs.

Afin d'être complet dans l'explication des trois principales méthodes utilisées dans la programmation fonctionnelle, j'expliquerai brièvement reduce() ainsi que quelques cas d'utilisation.

reduce() a la syntaxe suivante :

functools.reduce(function, iterable[, initializer])

La documentation Python fait référence au premier argument de reduce() comme « une fonction de deux arguments ». Cependant, nous pouvons passer n'importe quel Python appelable tant qu'il y a deux arguments. Les objets appelables incluent les classes, les méthodes d'instance, les méthodes de classe, les méthodes statiques et les fonctions.

Le deuxième argument obligatoire, iterable , peut être n'importe quel itérable Python. Le glossaire officiel de Python définit un itérable comme « un objet capable de renvoyer ses membres un par un. Les exemples d'itérables incluent tous les types de séquence (tels que list, str et tuple) et certains types non séquentiels tels que dict, les objets de fichier et les objets de toutes les classes que vous définissez avec une méthode __iter__() ou avec une méthode __getitem__() qui implémente la sémantique de séquence.”

Le initializer argument de reduce() est facultatif. Si vous fournissez une valeur à l'initialiseur, alors reduce() l'alimentera au premier appel de la fonction de son premier argument. Sinon, il utilisera la première valeur de l'itérable.

Si vous souhaitez utiliser reduce() pour traiter les itérables qui peuvent être vides, il est recommandé de fournir une valeur à l'initialiseur. Cette valeur sera utilisée comme valeur de retour par défaut lorsque le iterable est vide. Si vous ne fournissez aucune valeur, reduce() lèvera une TypeError.

Exécutons quelques exemples. Comme pour la section précédente, nous pouvons utiliser reduce() pour calculer les dépenses annuelles :

>>> from functools import reduce
>>> yearly_spendings = reduce(lambda x, y:x + y, monthly_spendings.values())
>>> print(yearly_spendings)
496

Les exemples ci-dessous sont plus difficiles, mais ils sont utiles reduce() cas d'utilisation. N'hésitez pas à jouer un peu avec le code pour vous familiariser avec les concepts.

Nous voulons transformer une liste de [[1, 3, 5], [7, 9], [11, 13, 15]] en [1, 3, 5, 7, 9, 11, 13, 15] .

Nous pouvons le faire comme suit :

>>> from functools import reduce
>>> reduce(list.__add__, [[1, 3, 5], [7, 9], [11, 13, 15]], [])
[1, 3, 5, 7, 9, 11, 13, 15]

Nous pouvons également utiliser reduce() pour trouver l'intersection de n nombre de listes. Par exemple :

>>> from functools import reduce

>>> num = [[5, 7, 8, 10, 3], [5, 12, 45, 8, 9], [8, 39, 90, 5, 12]]

>>> res = reduce(set.intersection, map(set, num))
>>> print(res)
{8, 5}

La sortie est un ensemble. Vous pouvez trouver plus d'informations sur les ensembles en Python ici.

Malgré les exemples cités ci-dessus, le nombre de reduce() les cas d'utilisation sont minimes, ce qui explique pourquoi il a été supprimé des fonctions intégrées de Python 3. La plupart du temps, vous ferez mieux d'utiliser une autre méthode pour manipuler les listes en Python.

Réflexions finales sur les flux Python

Dans cet article, vous avez découvert la programmation fonctionnelle en Python et ses trois méthodes principales, map() , filter() , et reduce() . Vous pouvez les utiliser pour manipuler des listes en Python. Nous avons également discuté de l'utilisation de groupby() et sort() .

Toutes ces méthodes facilitent le travail sur les flux en Python. Je vous encourage à jouer avec eux, à explorer ce qu'ils font et à comparer les résultats. Vous pouvez également découvrir plus de ressources sur LearnPython.com pour en savoir plus sur Python en général.