Est-ce pythonique d'utiliser les compréhensions de liste uniquement pour les effets secondaires?


108

Pensez à une fonction que j'appelle pour ses effets secondaires, pas pour ses valeurs de retour (comme l'impression à l'écran, la mise à jour de l'interface graphique, l'impression dans un fichier, etc.).

def fun_with_side_effects(x):
    ...side effects...
    return y

Maintenant, est-ce Pythonic d'utiliser les compréhensions de liste pour appeler cette fonction:

[fun_with_side_effects(x) for x in y if (...conditions...)]

Notez que je ne sauvegarde la liste nulle part

Ou devrais-je appeler cette func comme ceci:

for x in y:
    if (...conditions...):
        fun_with_side_effects(x)

Quel est le meilleur et pourquoi?


6
c'est limite, mais vous obtiendrez probablement plus d'opposition que de soutien. Je vais m'asseoir celui-ci: ^)
jcomeau_ictx

6
C'est un choix facile. La lisibilité compte - faites-le de la deuxième façon. Si vous ne pouvez pas insérer 2 lignes supplémentaires sur votre écran, obtenez un écran plus grand :)
John La Rooy

1
La compréhension de la liste est impythonique car elle viole «l'explicite vaut mieux qu'implicite» - vous cachez une boucle dans une construction différente.
Fred Foo

3
@larsmans: si seulement GvR avait réalisé ça quand il a introduit les list comprehensions en premier lieu!
Steve Jessop

2
@larsmans, Steve Jessop, je pense qu'il est incorrect de concevoir une compréhension de liste comme une boucle. Il peut très bien être implémenté comme une boucle, mais le but de telles constructions est d'opérer sur des données agrégées de manière fonctionnelle et (conceptuellement) parallèle. S'il y a un problème avec la syntaxe, c'est qu'il for ... inest utilisé dans les deux cas - ce qui conduit à des questions comme celle-ci!
senderle

Réponses:


84

C'est très anti-pythonique de le faire, et tout Pythonista chevronné vous en donnera l'enfer. La liste intermédiaire est jetée après sa création et elle peut être potentiellement très, très volumineuse et donc coûteuse à créer.


5
Alors, quelle serait une manière plus pythonique?
Joachim Sauer le

6
Celui qui ne tient pas la liste; c'est-à-dire une variante de la deuxième façon (j'ai été connu pour utiliser un genex dans l' foravant, pour me débarrasser de la if).
Ignacio Vazquez-Abrams

6
@Joachim Sauer: Exemple 2 ci-dessus. Une boucle de compréhension correcte, explicite et sans liste. Explicite. Clair. Évident.
S.Lott

31

Vous ne devriez pas utiliser une compréhension de liste , car comme les gens l'ont dit, cela créera une longue liste temporaire dont vous n'avez pas besoin. Les deux méthodes suivantes sont équivalentes:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

avec la définition de consumedepuis la itertoolspage de manuel:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

Bien sûr, ce dernier est plus clair et plus facile à comprendre.


@Paul: Je pense que ça devrait l'être. Et en effet, vous pouvez, bien que mapcela ne soit pas aussi intuitif si vous n'avez pas encore fait de programmation fonctionnelle.
Katriel

4
Je ne suis pas sûr que ce soit particulièrement idiomatique. Il n'y a aucun avantage à utiliser la boucle explicite.
Marcin

1
La solution estconsume = collections.deque(maxlen=0).extend
PaulMcG

24

Les compréhensions de listes servent à créer des listes. Et à moins que vous ne créiez réellement une liste, vous ne devez pas utiliser de compréhension de liste.

Donc, j'aurais pour la deuxième option, juste itérer sur la liste, puis appeler la fonction lorsque les conditions s'appliquent.


6
J'irais encore plus loin et déclarer que les effets secondaires dans la compréhension d'une liste sont inhabituels, inattendus et donc mauvais, même si vous utilisez la liste résultante lorsque vous avez terminé.
Mark Ransom

11

Le deuxième est meilleur.

Pensez à la personne qui aurait besoin de comprendre votre code. Vous pouvez facilement obtenir un mauvais karma avec le premier :)

Vous pouvez aller au milieu entre les deux en utilisant filter (). Prenons l'exemple:

y=[1,2,3,4,5,6]
def func(x):
    print "call with %r"%x

for x in filter(lambda x: x>3, y):
    func(x)

10
Votre lambda est beaucoup mieux écrit comme lambda x : x > 3.
PaulMcG

Vous n'avez même pas besoin de filtre. Il suffit de mettre une expression de générateur parens ici: for el in (x for x in y if x > 3):. elet xpeut avoir le même nom, mais cela pourrait dérouter les gens.
Omnifarious

3

Cela dépend de votre objectif.

Si vous essayez d'effectuer une opération sur chaque objet d'une liste, la deuxième approche doit être adoptée.

Si vous essayez de générer une liste à partir d'une autre liste, vous pouvez utiliser la compréhension de liste.

Explicite vaut mieux qu'implicite. Le simple vaut mieux que le complexe. (Python Zen)


0

Tu peux faire

for z in (fun_with_side_effects(x) for x in y if (...conditions...)): pass

mais ce n'est pas très joli.


-1

Utiliser une compréhension de liste pour ses effets secondaires est moche, non pythonique, inefficace et je ne le ferais pas. J'utiliserais forplutôt une boucle, car une forboucle signale un style procédural dans lequel les effets secondaires sont importants.

Mais, si vous insistez absolument pour utiliser une compréhension de liste pour ses effets secondaires, vous devez éviter l'inefficacité en utilisant plutôt une expression de générateur. Si vous insistez absolument sur ce style, faites l'un de ces deux:

any(fun_with_side_effects(x) and False for x in y if (...conditions...))

ou:

all(fun_with_side_effects(x) or True for x in y if (...conditions...))

Ce sont des expressions génératrices et elles ne génèrent pas de liste aléatoire qui est jetée. Je pense que le allformulaire est peut-être un peu plus clair, même si je pense que les deux sont déroutants et ne devraient pas être utilisés.

Je pense que c'est moche et je ne le ferais pas réellement dans le code. Mais si vous insistez pour implémenter vos boucles de cette façon, c'est comme ça que je le ferais.

J'ai tendance à penser que les compréhensions de listes et leurs semblables devraient signaler une tentative d'utiliser quelque chose qui ressemble au moins légèrement à un style fonctionnel. Mettre des choses avec des effets secondaires qui cassent cette hypothèse obligera les gens à lire votre code plus attentivement, et je pense que c'est une mauvaise chose.


Et si fun_with_side_effectsrenvoie Vrai?
Katriel

7
Je pense que ce remède est pire que la maladie - itertools.consume est beaucoup plus propre.
PaulMcG

@PaulMcG - itertools.consumen'existe plus, probablement parce qu'utiliser des compréhensions avec des effets secondaires est moche.
Omnifarious

1
Il s'avère que je me suis trompé et que cela n'a jamais existé en tant que méthode dans la stdlib. Il est une recette dans les documents itertools: docs.python.org/3/library/...
PaulMcG
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.