Question liste compréhension vs lambda + filtre


Je me suis retrouvé à avoir un besoin de filtrage de base: j'ai une liste et je dois le filtrer par un attribut des éléments.

Mon code ressemblait à ceci:

my_list = [x for x in my_list if x.attribute == value]

Mais alors j'ai pensé, ne serait-il pas mieux de l'écrire comme ça?

my_list = filter(lambda x: x.attribute == value, my_list)

C'est plus lisible, et si nécessaire pour la performance, le lambda pourrait être retiré pour gagner quelque chose.

La question est la suivante: y a-t-il des restrictions à l'utilisation de la deuxième voie? Toute différence de performance? Ai-je complètement manqué le Pythonic Way et devrait le faire d'une autre manière (comme utiliser itemgetter au lieu du lambda)?


606
2018-06-10 10:14


origine


Réponses:


Il est étrange combien la beauté varie pour différentes personnes. Je trouve la compréhension de la liste beaucoup plus claire que filter+lambda, mais utilisez ce que vous trouvez plus facile. Cependant, arrêtez de donner vos noms de variables déjà utilisés pour les built-ins, c'est déroutant.

Il y a deux choses qui peuvent ralentir votre utilisation de filter.

Le premier est la surcharge de l'appel de fonction: dès que vous utilisez une fonction Python (qu'elle soit créée par def ou lambda) il est probable que le filtre sera plus lent que la compréhension de la liste. Il ne suffit certainement pas d'avoir de l'importance, et vous ne devriez pas trop penser à la performance tant que vous n'avez pas chronométré votre code et constaté qu'il s'agit d'un goulot d'étranglement, mais la différence sera là.

L'autre surcharge qui pourrait s'appliquer est que le lambda est forcé d'accéder à une variable de portée (value). C'est plus lent que d'accéder à une variable locale et en Python 2.x la compréhension de la liste n'accède qu'aux variables locales. Si vous utilisez Python 3.x, la compréhension de la liste s'exécute dans une fonction séparée, de sorte qu'elle accède également value à travers une fermeture et cette différence ne s'appliquera pas.

L'autre option à considérer est d'utiliser un générateur au lieu d'une compréhension de liste:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

Ensuite, dans votre code principal (où la lisibilité est vraiment importante), vous avez remplacé à la fois la compréhension de la liste et le filtrage avec un nom de fonction significatif.


419
2018-06-10 10:52



C'est un problème un peu religieux dans Python. Même si Guido a envisagé de supprimer map, filter et reduce de Python 3, il y avait assez de jeu à la fin seulement reduce a été déplacé de built-ins à functools.reduce.

Personnellement, je trouve les listes compréhensibles plus faciles à lire. Il est plus explicite ce qui se passe à partir de l'expression [i for i in list if i.attribute == value] comme tout le comportement est sur la surface pas à l'intérieur de la fonction de filtre.

Je ne m'inquiéterais pas trop de la différence de performance entre les deux approches car elle est marginale. Je ne ferais que l'optimiser si cela s'avérait être le goulot d'étranglement dans votre application, ce qui est peu probable.

Aussi depuis le BDFL voulait filter parti de la langue alors sûrement cela rend automatiquement la compréhension des listes plus Pythonic ;-)


183
2018-06-10 10:58



Étant donné que toute différence de vitesse est forcément minuscule, l'utilisation de filtres ou la compréhension de listes se résume à une question de goût. En général, je suis enclin à utiliser des compréhensions (ce qui semble être en accord avec la plupart des autres réponses ici), mais il y a un cas où je préfère filter.

Un cas d'utilisation très fréquent consiste à extraire les valeurs de quelque X itérable soumis à un prédicat P (x):

[x for x in X if P(x)]

mais parfois vous voulez appliquer une fonction aux valeurs d'abord:

[f(x) for x in X if P(f(x))]


À titre d'exemple spécifique, considérez

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

Je pense que cela semble légèrement mieux que d'utiliser filter. Mais maintenant considérez

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

Dans ce cas, nous voulons filter par rapport à la valeur post-calculée. Outre la question du calcul du cube deux fois (imaginez un calcul plus coûteux), il y a la question de l'écriture de l'expression deux fois, en violation de la SEC esthétique. Dans ce cas, je serais susceptible d'utiliser

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

50
2017-11-13 20:00



Bien que filter peut être le "chemin le plus rapide", le "chemin Pythonique" ne se soucierait pas de telles choses à moins que la performance soit absolument critique (dans ce cas, vous n'utiliserez pas Python!).


25
2018-06-10 10:22



Je pensais que j'ajouterais que dans python 3, filter () est en fait un objet itérateur, donc vous devrez passer votre appel de méthode filter à list () pour construire la liste filtrée. Donc, en python 2:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

les listes b et c ont les mêmes valeurs et ont été complétées à peu près en même temps que filter () était équivalent [x pour x dans y si z]. Cependant, en 3, ce même code laisserait la liste c contenant un objet filtre, pas une liste filtrée. Pour produire les mêmes valeurs en 3:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

Le problème est que list () prend un itérable comme argument, et crée une nouvelle liste à partir de cet argument. Le résultat est que l'utilisation de ce filtre dans python 3 prend deux fois plus de temps que la méthode [x for x in y if] car vous devez parcourir la sortie de filter () ainsi que la liste d'origine.


13
2017-09-06 06:26



Une différence importante est que la compréhension de la liste retournera un list tandis que le filtre renvoie un filter, que vous ne pouvez pas manipuler comme un list (c'est-à-dire: appel len dessus, qui ne fonctionne pas avec le retour de filter).

Mon auto-apprentissage m'a amené à un problème similaire.

Cela étant dit, s'il y a un moyen d'avoir le résultat list de filter, un peu comme vous le feriez dans .NET quand vous faites lst.Where(i => i.something()).ToList(), Je suis curieux de le savoir.

EDIT: C'est le cas de Python 3, pas de 2 (voir la discussion dans les commentaires).


9
2017-10-15 23:50



Je trouve la deuxième façon plus lisible. Il vous dit exactement ce que l'intention est: filtrer la liste.
PS: n'utilisez pas 'list' comme nom de variable


8
2018-06-10 10:19



Filtre c'est juste ça. Il filtre les éléments d'une liste. Vous pouvez voir la définition mentionne la même chose (dans le lien officiel des documents que j'ai mentionné auparavant). Alors que la compréhension de listes est quelque chose qui produit une nouvelle liste après avoir agi quelque chose dans la liste précédente (la compréhension du filtre et de la liste crée une nouvelle liste et ne fonctionne pas à la place de l'ancienne liste.) Une nouvelle liste ressemble à une liste avec, disons, un type de données entièrement nouveau. etc)

Dans votre exemple, il est préférable d'utiliser un filtre que la compréhension de la liste, conformément à la définition. Toutefois, si vous le souhaitez, dites other_attribute parmi les éléments de la liste, dans votre exemple, vous devez les récupérer sous la forme d'une nouvelle liste, vous pouvez alors utiliser la compréhension de la liste.

return [item.other_attribute for item in my_list if item.attribute==value]

C'est ainsi que je me souviens de la compréhension des filtres et des listes. Supprimez quelques éléments dans une liste et gardez les autres éléments intacts, utilisez le filtre. Utilisez une certaine logique sur les éléments et créer une liste diluée adaptée à un usage particulier, utilisez la compréhension de la liste.


7
2018-01-29 07:32



généralement filter est légèrement plus rapide si vous utilisez une fonction intégrée.

Je m'attendrais à ce que la compréhension de la liste soit légèrement plus rapide dans votre cas


5
2018-06-10 10:17