Comment utiliser le filtre, la carte et la réduction dans Python 3

321

filter,, mapet reducefonctionne parfaitement en Python 2. Voici un exemple:

>>> def f(x):
        return x % 2 != 0 and x % 3 != 0
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

>>> def cube(x):
        return x*x*x
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

>>> def add(x,y):
        return x+y
>>> reduce(add, range(1, 11))
55

Mais en Python 3, je reçois les sorties suivantes:

>>> filter(f, range(2, 25))
<filter object at 0x0000000002C14908>

>>> map(cube, range(1, 11))
<map object at 0x0000000002C82B70>

>>> reduce(add, range(1, 11))
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    reduce(add, range(1, 11))
NameError: name 'reduce' is not defined

J'apprécierais que quelqu'un m'explique pourquoi.

Capture d'écran du code pour plus de clarté:

Sessions IDLE côte à côte de Python 2 et 3

Dick Lucas
la source
1
En bref, la liste n'est pas le seul type de données. Si vous voulez une liste, dites que vous voulez une liste. Mais dans la plupart des cas, vous voulez quand même quelque chose d'autre.
Veky

Réponses:

346

Vous pouvez en savoir plus sur les modifications dans Quoi de neuf dans Python 3.0 . Vous devriez le lire attentivement lorsque vous passez de 2.x à 3.x car beaucoup de choses ont été modifiées.

La réponse entière ici sont des citations de la documentation.

Vues et itérateurs au lieu de listes

Certaines API bien connues ne renvoient plus de listes:

  • [...]
  • map()et filter()renvoyer les itérateurs. Si vous avez vraiment besoin d'une liste, une solution rapide est par exemple list(map(...)), mais une meilleure solution consiste souvent à utiliser une compréhension de liste (en particulier lorsque le code d'origine utilise lambda), ou à réécrire le code afin qu'il n'ait pas besoin d'une liste du tout. Particulièrement délicat est map()invoqué pour les effets secondaires de la fonction; la transformation correcte consiste à utiliser une forboucle régulière (car créer une liste serait tout simplement inutile).
  • [...]

Builtins

  • [...]
  • Supprimé reduce(). À utiliser functools.reduce()si vous en avez vraiment besoin; cependant, 99% du temps, une forboucle explicite est plus lisible.
  • [...]
nhahtdh
la source
21
L'ajout list(map(...) partout .. comment dans le monde est que la lisibilité aider .. pythonne semble pas gérer l'application progressive / streaming de combinateurs fonctionnels. Autres langues Je peux enchaîner une douzaine d'opérations contre une collection de suite et c'est lisible. Ici? que voulez-vous - une douzaine de voies imbriquées in??
javadba
11
Si vous travaillez dans un contexte impératif, une boucle for est probablement l'option la plus lisible. Mais il y a de bonnes raisons de préférer un contexte fonctionnel - et rompre avec cela pour revenir à la procédure peut être sacrément laid.
MatrixManAtYrService
2
@javadba Êtes-vous sûr que dans une "application de streaming" vous devez ajouter l' listappel du tout? Je pensais que la signification de "streaming" est "aucune liste n'est créée du tout; traitez chaque élément de l'entrée complètement avant de passer au suivant".
Nuit impérissable
@MatrixManAtYrService Si vous êtes sûr que le comportement de python 2 est ce dont vous avez besoin, vous pouvez toujours simplement redéfinir map.
Nuit impérissable
6
Je n'arrive toujours pas à comprendre comment un argument de lisibilité conduit à un tel changement. Si c'était pour des raisons de performances, je pourrais comprendre ...
Minato
86

La fonctionnalité de mapet a filterété intentionnellement modifiée pour renvoyer les itérateurs, et la réduction a été supprimée d'être intégrée et placée functools.reduce.

Donc, pour filteret map, vous pouvez les envelopper list()pour voir les résultats comme vous l'avez fait auparavant.

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> list(filter(f, range(2, 25)))
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> list(map(cube, range(1, 11)))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> import functools
>>> def add(x,y): return x+y
...
>>> functools.reduce(add, range(1, 11))
55
>>>

La recommandation est maintenant de remplacer votre utilisation de la carte et du filtre par des expressions de générateurs ou des compréhensions de liste. Exemple:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> [i for i in range(2, 25) if f(i)]
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> [cube(i) for i in range(1, 11)]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>

Ils disent que pour les boucles, il est 99% plus facile à lire qu'à réduire, mais je resterais avec functools.reduce.

Edit : le chiffre de 99% est tiré directement de la page Quoi de neuf en Python 3.0 rédigée par Guido van Rossum.

Joshua D. Boyd
la source
5
Vous n'avez pas besoin de créer de fonctions supplémentaires dans les compréhensions de liste. Il suffit d'utiliser[i*i*i for i in range(1,11)]
Xiao
2
Vous avez tout à fait raison. J'ai gardé la fonction dans les exemples de compréhension de la liste pour qu'elle reste similaire aux exemples de filtre / carte.
Joshua D. Boyd
5
i ** 3 est également équivalent à i * i * i
Breezer
5
@Breezer i**3va en fait appeler i.__pow__(3)et i*i*i i.__mul__(i).__mul__(i)(ou quelque chose comme ça). Avec ints, cela n'a pas d'importance, mais avec des nombres numpy / des classes personnalisées, cela peut même produire des résultats différents.
syntonym
1
J'ai remarqué que chaque fois que nous entendons que "Guido a pris la décision X", la douleur est un résultat probable. Ceci est un excellent exemple: list(list(list(.. )))faire ce qui était déjà verbeux en python.
javadba
12

En complément des autres réponses, cela ressemble à un bon cas d'utilisation pour un gestionnaire de contexte qui remappera les noms de ces fonctions à celles qui renvoient une liste et introduisent reducedans l'espace de noms global.

Une implémentation rapide pourrait ressembler à ceci:

from contextlib import contextmanager    

@contextmanager
def noiters(*funcs):
    if not funcs: 
        funcs = [map, filter, zip] # etc
    from functools import reduce
    globals()[reduce.__name__] = reduce
    for func in funcs:
        globals()[func.__name__] = lambda *ar, func = func, **kwar: list(func(*ar, **kwar))
    try:
        yield
    finally:
        del globals()[reduce.__name__]
        for func in funcs: globals()[func.__name__] = func

Avec une utilisation qui ressemble à ceci:

with noiters(map):
    from operator import add
    print(reduce(add, range(1, 20)))
    print(map(int, ['1', '2']))

Qui imprime:

190
[1, 2]

Juste mes 2 cents :-)

Dimitris Fasarakis Hilliard
la source
1
pythoncomme langue est un gâchis - mais il a v bon d'excellentes bibliothèques: numpy, pandas, statsmodelset les amis .. J'avais Buliding bibliothèques de proximité comme vous montrer ici pour réduire la douleur de la langue maternelle - mais ont perdu l'énergie et essayer de ne pas s'éloigner loin d'un data.frame/ datatable, ou xarray. Mais bravo pour avoir essayé ..
javadba
7

Puisque la reduceméthode a été supprimée de la fonction intégrée de Python3, n'oubliez pas d'importer le functoolsdans votre code. Veuillez consulter l'extrait de code ci-dessous.

import functools
my_list = [10,15,20,25,35]
sum_numbers = functools.reduce(lambda x ,y : x+y , my_list)
print(sum_numbers)
Bikash Singh
la source
2

Voici les exemples de fonctions Filtrer, mapper et réduire.

nombres = [10,11,12,22,34,43,54,34,67,87,88,98,99,87,44,66]

//Filtre

oddNumbers = list (filtre (lambda x: x% 2! = 0, nombres))

print (oddNumbers)

//Carte

multiplyOf2 = liste (carte (lambda x: x * 2, nombres))

imprimer (multiplyOf2)

//Réduire

La fonction de réduction, car elle n'est pas couramment utilisée, a été supprimée des fonctions intégrées de Python 3. Elle est toujours disponible dans le module functools, vous pouvez donc faire:

de functools import réduire

sumOfNumbers = réduire (lambda x, y: x + y, nombres)

print (sumOfNumbers)

Yogendra Singh
la source
0

L'un des avantages de la cartographie, du filtrage et de la réduction est leur lisibilité lorsque vous les "enchaînez" ensemble pour faire quelque chose de complexe. Cependant, la syntaxe intégrée n'est pas lisible et est tout "en arrière". Je suggère donc d'utiliser le PyFunctionalpackage ( https://pypi.org/project/PyFunctional/ ). Voici une comparaison des deux:

flight_destinations_dict = {'NY': {'London', 'Rome'}, 'Berlin': {'NY'}}

Version PyFunctional

Syntaxe très lisible. Tu peux dire:

"J'ai une séquence de destinations de vol. D'où je veux obtenir la clé dict si la ville est dans les valeurs dict. Enfin, filtrez les listes vides que j'ai créées dans le processus."

from functional import seq  # PyFunctional package to allow easier syntax

def find_return_flights_PYFUNCTIONAL_SYNTAX(city, flight_destinations_dict):
    return seq(flight_destinations_dict.items()) \
        .map(lambda x: x[0] if city in x[1] else []) \
        .filter(lambda x: x != []) \

Version Python par défaut

Tout est en arrière. Vous devez dire:

"OK, donc, il y a une liste. Je veux en filtrer les listes vides. Pourquoi? Parce que j'ai d'abord obtenu la clé dict si la ville était dans les valeurs dict. Oh, la liste à laquelle je fais cela est flight_destinations_dict. "

def find_return_flights_DEFAULT_SYNTAX(city, flight_destinations_dict):
    return list(
        filter(lambda x: x != [],
               map(lambda x: x[0] if city in x[1] else [], flight_destinations_dict.items())
               )
    )
Daniel
la source