web-dev-qa-db-fra.com

compréhension de la liste par rapport au filtre lambda +

Il m'est arrivé de me trouver confronté à un besoin élémentaire de filtrage: j'ai une liste et je dois la filtrer en fonction d'un attribut des éléments.

Mon code ressemblait à ceci:

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

Mais ensuite j'ai pensé, ne serait-il pas préférable de l'écrire comme ça?

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

Il est plus lisible et, si nécessaire, le lambda peut être retiré pour gagner quelque chose. 

La question est: y at-il des mises en garde concernant l’utilisation de la deuxième manière? Toute différence de performance? Est-ce que je manque complètement le Pythonic Way ™ et devrais le faire d'une autre manière (comme utiliser itemgetter au lieu du lambda)?

703
Agos

Il est étrange que la beauté varie d'une personne à l'autre. Je trouve la compréhension de la liste beaucoup plus claire que filter + lambda, mais utilisez celle que vous trouvez plus facile. Cependant, arrêtez de donner à vos variables des noms déjà utilisés pour les fonctions intégrées, ce qui est source de confusion. [ La question à l'origine utilisait list comme nom de variable mais a été mise à jour en réponse à cette réponse. ]

Deux choses peuvent ralentir votre utilisation de filter.

La première est la surcharge de l'appel de fonction: dès que vous utilisez une fonction Python (créée par def ou lambda), il est probable que le filtre sera plus lent que la compréhension de la liste. Cela n’est presque certainement pas suffisant, et vous ne devriez pas trop penser aux performances tant que vous n’avez pas chronométré votre code et constaté qu’il s’agissait d’un goulot d’étranglement, mais la différence sera là.

L'autre surcharge qui pourrait s'appliquer est que le lambda est obligé d'accéder à une variable de portée (value). C'est plus lent que d'accéder à une variable locale et dans 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 est exécutée dans une fonction distincte. Elle va donc également accéder à value par le biais d'une clôture et cette différence ne s'appliquera pas.

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

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

Ensuite, dans votre code principal (où la lisibilité compte vraiment), vous avez remplacé la compréhension de la liste et le filtre par un nom de fonction qui, espérons-le, aurait du sens.

486
Duncan

C'est un problème quelque peu religieux en Python. Même si Guido envisageait de supprimer map, filter et reduce de Python 3, il y a eu suffisamment de contrecoups pour que seul reduce ait été déplacé des fonctions intégrées à functools.reduce .

Personnellement, je trouve la compréhension des listes plus facile à lire. C'est plus explicite ce qui se passe à partir de l'expression [i for i in list if i.attribute == value] car tout le comportement est visible à la surface, pas à l'intérieur de la fonction de filtrage.

Je ne m'inquiéterais pas trop de la différence de performance entre les deux approches car elle est marginale. Je ne l’optimaliserais vraiment que si cela posait problème, ce qui est peu probable dans votre application.

Aussi, puisque le BDFL voulait filter partir de la langue, alors cela rendrait automatiquement les listes plus pythoniques ;-)

201
Tendayi Mawushe

Etant donné que toute différence de vitesse est forcément minime, l’utilisation de filtres ou de listes de compréhension est 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 certains X itérables soumis à un prédicat P (x):

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

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

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


À titre d'exemple, considérons

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 reprises (imaginez un calcul plus coûteux), il existe le problème de l'écriture de l'expression deux fois, en violation de l'esthétique SÈCHE . Dans ce cas, je serais apte à utiliser

prime_cubes = filter(prime, [x*x*x for x in range(1000)])
60
I. J. Kennedy

Bien que filter puisse être le "moyen le plus rapide", le "moyen Pythonique" consisterait à ne pas se soucier de telles choses sauf si les performances sont absolument essentielles (dans ce cas, vous n'utiliseriez pas Python!).

27
Umang

Je pensais ajouter que dans python 3, filter () est en fait un objet itérateur. Vous devez donc passer votre appel de méthode de filtrage à list () afin de générer 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 au même moment où filter () était équivalent [x pour x dans y si z]. Cependant, dans 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 type de filtre dans Python 3 prend jusqu'à deux fois plus de temps que la méthode [x pour x dans y si z] car vous devez itérer sur la sortie de filter () ainsi que sur la liste d'origine. 

15
Jim50

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

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

Cela étant dit, s’il existe un moyen d’obtenir la list résultante d’une filter, un peu comme vous le feriez dans .NET lorsque vous exécuterez lst.Where(i => i.something()).ToList(), je suis curieux de le savoir.

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

10
Adeynack

Je trouve le deuxième moyen plus lisible. Il vous dit exactement quelle est l'intention: filtrer la liste.
PS: ne pas utiliser 'list' comme nom de variable

9
unbeli

Filtre est juste que. Il filtre les éléments d'une liste. Vous pouvez voir que la définition mentionne la même chose (dans le lien de documentation officiel que j'ai mentionné précédemment). Alors que la compréhension de liste est quelque chose qui produit une nouvelle liste après avoir utilisé quelque chose de la liste précédente (la compréhension du filtre et de la liste crée une nouvelle liste et ne permet pas d’opérer à la place de la liste plus ancienne. comme une liste avec, par exemple, un type de données entièrement nouveau, comme la conversion d'entiers en chaîne, etc.

Dans votre exemple, il est préférable d’utiliser le filtre plutôt que la compréhension de la liste, conformément à la définition. Toutefois, si vous voulez, par exemple, attribut autre_attribut à partir des éléments de la liste, vous pouvez, dans votre exemple, le récupérer sous forme de nouvelle liste. Vous pouvez alors utiliser la compréhension de 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 de la liste et conservez les autres éléments, utilisez le filtre. Utilisez un peu de logique vous-même au niveau des éléments et créez une liste diluée adaptée à un usage particulier, utilisez la compréhension par liste.

7
thiruvenkadam

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 

6
John La Rooy

Voici un court article que j’utilise lorsque je dois filtrer sur quelque chose après la compréhension de la liste. Juste une combinaison de filtre, lambda et listes (autrement appelée loyauté d'un chat et propreté d'un chien).

Dans ce cas, je lis un fichier en supprimant les lignes vides, les lignes commentées et tout ce qui suit un commentaire sur une ligne:

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]
5
rharder

En plus de la réponse acceptée, il existe un cas de figure dans lequel vous devez utiliser un filtre au lieu d'une compréhension de liste. Si la liste n'est pas fastidieuse, vous ne pouvez pas la traiter directement avec une compréhension de la liste. Par exemple, si vous utilisez pyodbc pour lire les résultats d'une base de données. Le fetchAll() résultant de cursor est une liste imparable. Dans cette situation, pour manipuler directement sur les résultats renvoyés, le filtre doit être utilisé:

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

Si vous utilisez la compréhension de liste ici, vous obtiendrez l'erreur:

TypeError: type inshashable: 'list'

4
C.W.praen

Il m'a fallu un peu de temps pour me familiariser avec les higher order functionsfilter et map. Donc je me suis habitué à eux et j'ai en fait aimé filter car il était explicite que cela filtre en conservant tout ce qui est véridique et je me suis senti bien de savoir que je connaissais des termes functional programming

Ensuite, j'ai lu ce passage (Fluent Python Book): 

Les fonctions de carte et de filtre sont toujours intégrées en Python 3, mais depuis l’introduction de la compréhension de liste et du générateur ex ‐ pressions, ils ne sont pas aussi importants. Un listcomp ou un genexp fait le travail de map et filtre combiné, mais est plus lisible. 

Et maintenant, je pense, pourquoi s’embêter avec le concept de filter/map si vous pouvez le réaliser avec des idiomes déjà largement répandus, comme la compréhension de listes. De plus, maps et filters sont des types de fonctions. Dans ce cas, je préfère utiliser Anonymous functions lambdas. 

Enfin, juste pour le tester, j’ai chronométré les deux méthodes (map et listComp) et je n’ai pas vu de différence de vitesse pertinente qui justifierait une argumentation à ce sujet. 

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602
3
user1767754

Curieusement sur Python 3, je vois un filtre performant plus rapidement que la compréhension de liste.

J'ai toujours pensé que la compréhension de la liste serait plus performante ... Quelque chose comme: [Nom pour nom dans brand_names_db si nom n'est pas None].

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

Mais ils sont en réalité plus lents:

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214
0
Rod Senra