Manière pythonique de combiner la boucle FOR et l'instruction IF


266

Je sais comment utiliser à la fois les boucles for et les instructions if sur des lignes distinctes, telles que:

>>> a = [2,3,4,5,6,7,8,9,0]
... xyz = [0,12,4,6,242,7,9]
... for x in xyz:
...     if x in a:
...         print(x)
0,4,6,7,9

Et je sais que je peux utiliser une compréhension de liste pour les combiner lorsque les déclarations sont simples, telles que:

print([x for x in xyz if x in a])

Mais ce que je ne trouve pas est un bon exemple n'importe où (pour copier et apprendre) démontrant un ensemble complexe de commandes (pas seulement "print x") qui se produisent après une combinaison d'une boucle for et de quelques instructions if. Quelque chose que j'attendrais ressemble à:

for x in xyz if x not in a:
    print(x...)

N'est-ce pas ainsi que le python est censé fonctionner?


23
C'est comme ça ... ne compliquez pas les choses en essayant de les simplifier. Pythonic ne signifie pas éviter chaque forboucle et ifinstruction explicites .
Felix Kling

2
Vous pouvez utiliser la liste générée dans votre compréhension de liste dans une boucle for. Cela ressemblerait un peu à votre dernier exemple.
Jacob

Donc, pour en savoir plus sur le traitement, quel est le moyen le plus rapide de combiner une boucle for avec une instruction if, si l'instruction if exclut les valeurs qui ont déjà été mises en correspondance et que la liste augmente continuellement pendant l'itération de la boucle for?
ChewyChunks

3
@Chewy, les structures de données appropriées rendront le code plus rapide, pas le sucre syntaxique. Par exemple, x in aest lent s'il as'agit d'une liste.
Nick Dandoulakis

1
C'est Python, un langage interprété; pourquoi quelqu'un discute-t-il de la vitesse du code?
ArtOfWarfare

Réponses:


323

Vous pouvez utiliser des expressions de générateur comme ceci:

gen = (x for x in xyz if x not in a)

for x in gen:
    print x

1
gen = (y for (x,y) in enumerate(xyz) if x not in a)renvoie >>> 12lorsque je tape for x in gen: print x- alors pourquoi le comportement inattendu avec énumérer?
ChewyChunks

9
Possible, mais pas plus joli que l'original pour et si les blocs.
Mike Graham

1
@ChewyChunks. Cela fonctionnerait, mais l'appel à énumérer est redondant.
Johnsyweb

132
Python me manque vraiment de pouvoir direfor x in xyz if x:
bgusach

10
for x in (x for x in xyz if x not in a):fonctionne pour moi, mais pourquoi vous ne devriez pas être en mesure de le faire for x in xyz if x not in a:, je ne suis pas sûr ...
Matt Wenham

34

Selon The Zen of Python (si vous vous demandez si votre code est "Pythonic", c'est l'endroit où aller):

  • Beau, c'est mieux que laid.
  • Explicite vaut mieux qu'implicite.
  • Simple, c'est mieux que complexe.
  • L'appartement est meilleur que l'emboîtement.
  • La lisibilité compte.

La façon Pythonique d'obtenir les deux s est:sorted intersectionset

>>> sorted(set(a).intersection(xyz))
[0, 4, 6, 7, 9]

Ou ces éléments qui sont xyzmais pas dans a:

>>> sorted(set(xyz).difference(a))
[12, 242]

Mais pour une boucle plus compliquée, vous pouvez l'aplatir en itérant sur une expression de générateur bien nommée et / ou en appelant une fonction bien nommée. Essayer de tout mettre sur une seule ligne est rarement "Pythonic".


Mise à jour suite aux commentaires supplémentaires sur votre question et la réponse acceptée

Je ne sais pas avec quoi vous essayez de faire enumerate, mais s'il as'agit d'un dictionnaire, vous voudrez probablement utiliser les clés, comme ceci:

>>> a = {
...     2: 'Turtle Doves',
...     3: 'French Hens',
...     4: 'Colly Birds',
...     5: 'Gold Rings',
...     6: 'Geese-a-Laying',
...     7: 'Swans-a-Swimming',
...     8: 'Maids-a-Milking',
...     9: 'Ladies Dancing',
...     0: 'Camel Books',
... }
>>>
>>> xyz = [0, 12, 4, 6, 242, 7, 9]
>>>
>>> known_things = sorted(set(a.iterkeys()).intersection(xyz))
>>> unknown_things = sorted(set(xyz).difference(a.iterkeys()))
>>>
>>> for thing in known_things:
...     print 'I know about', a[thing]
...
I know about Camel Books
I know about Colly Birds
I know about Geese-a-Laying
I know about Swans-a-Swimming
I know about Ladies Dancing
>>> print '...but...'
...but...
>>>
>>> for thing in unknown_things:
...     print "I don't know what happened on the {0}th day of Christmas".format(thing)
...
I don't know what happened on the 12th day of Christmas
I don't know what happened on the 242th day of Christmas

Cela ressemble aux commentaires ci-dessous, je devrais étudier les générateurs. Je ne les ai jamais utilisés. Merci. Un générateur est-il plus rapide que la combinaison équivalente d'instructions FOR et IF? J'ai également utilisé des ensembles, mais parfois des éléments redondants dans une liste sont des informations que je ne peux pas ignorer.
ChewyChunks

@ChewyChunks: Les générateurs ne sont pas le seul moyen d'être Pythonic!
Johnsyweb

3
@Johnsyweb, si vous allez citer le Zen of Python: "Il devrait y avoir une - et de préférence une seule - manière évidente de le faire."
Wooble

@Wooble: Il devrait y en avoir. J'ai cité cette section dans ma réponse à une autre question à la même époque!
Johnsyweb

18

Je pense personnellement que c'est la plus jolie version:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]
for x in filter(lambda w: w in a, xyz):
  print x

Éditer

si vous souhaitez éviter d'utiliser lambda, vous pouvez utiliser une application de fonction partielle et utiliser le module opérateur (qui fournit les fonctions de la plupart des opérateurs).

https://docs.python.org/2/library/operator.html#module-operator

from operator import contains
from functools import partial
print(list(filter(partial(contains, a), xyz)))

4
filter(a.__contains__, xyz). Habituellement, lorsque les gens utilisent lambda, ils ont vraiment besoin de quelque chose de beaucoup plus simple.
Veky

Je pense que vous avez mal compris quelque chose. __contains__est une méthode comme les autres, seulement c'est une méthode spéciale , ce qui signifie qu'elle peut être appelée indirectement par un opérateur ( indans ce cas). Mais il peut aussi être appelé directement, il fait partie de l'API publique. Les noms privés sont spécifiquement définis comme ayant au plus un trait de soulignement final, pour fournir une exception pour les noms de méthodes spéciales - et ils sont sujets à la manipulation de noms lorsqu'ils sont lexicalement dans les étendues de classe. Voir docs.python.org/3/reference/datamodel.html#specialnames et docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky

C'est certainement ok, mais deux importations juste pour pouvoir faire référence à une méthode accessible en utilisant juste un attribut semblent bizarres (les opérateurs sont généralement utilisés lorsque la double répartition est essentielle, mais inest distribuée séparément par l'opérande droit). En outre, notez que la méthode operatorexporte également containssous le nom __contains__, donc ce n'est certainement pas un nom privé. Je pense que vous devrez juste apprendre à vivre avec le fait que tous les doubles soulignés ne signifient pas "éloignez-vous". : -]
Veky

Je pense que vos lambdabesoins doivent inclure not: lambda w: not w in a, xyz
javadba

Le filtre semble plus élégant, en particulier pour les conditions complexes qui deviendraient des fonctions définies au lieu de lambdas, peut-être que nommer la fonction lambda ajouterait une certaine lisibilité, le générateur semble meilleur lorsque les éléments itérés sont des modifications sur les éléments de la liste
Khanis Rok

16

Ce qui suit est une simplification / une ligne de la réponse acceptée:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]

for x in (x for x in xyz if x not in a):
    print(x)

12
242

Notez que le a generatorété maintenu en ligne . Cela a été testé python2.7et python3.6 (notez les parens dans le print;))


10

J'utiliserais probablement:

for x in xyz: 
    if x not in a:
        print x...

@KirillTitov Oui, le python est un langage fondamentalement non fonctionnel (c'est un codage purement impératif - et je suis d'accord avec l'auteur de cette réponse que c'est la façon dont le python est configuré pour être écrit. Tenter d'utiliser des fonctionnels conduit à une lecture médiocre ou non pythonicJe peux coder fonctionnellement dans toutes les autres langues que j'utilise (scala, kotlin, javascript, R, swift, ..) mais difficile / maladroit en python
javadba

9
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]  
set(a) & set(xyz)  
set([0, 9, 4, 6, 7])

Très zen, @lazyr, mais ne m'aiderait pas à améliorer un bloc de code complexe qui dépend de l'itération dans une liste et de l'ignorance des éléments correspondants dans une autre liste. Est-il plus rapide de traiter la première liste comme un ensemble et de comparer l'union / la différence avec une seconde liste "ignorée" croissante?
ChewyChunks

Essaye ça import time a = [2,3,4,5,6,7,8,9,0] xyz = [0,12,4,6,242,7,9] start = time.time() print (set(a) & set(xyz)) print time.time() - start
Kracekumar

@ChewyChunks si l'une des listes change pendant l'itération, il sera probablement plus rapide de vérifier chaque élément par rapport à la liste d'ignorance - sauf que vous devriez en faire un ensemble d'ignorance. La vérification de l'appartenance à des ensembles est très rapide:if x in ignore: ... .
Lauritz V. Thaulow

@lazyr Je viens de réécrire mon code en utilisant un ensemble d' sur une liste d' . Semble traiter le temps beaucoup plus lentement. (Pour être honnête, je comparais en utilisant, if set(a) - set(ignore) == set([]):c'est peut-être pourquoi c'est beaucoup plus lent que de vérifier l'adhésion. Je testerai cela à l'avenir sur un exemple beaucoup plus simple que celui que j'écris.
ChewyChunks

5

Vous pouvez également utiliser des générateurs , si les expressions de générateur deviennent trop impliquées ou complexes:

def gen():
    for x in xyz:
        if x in a:
            yield x

for x in gen():
    print x

C'est un peu plus utile pour moi. Je n'ai jamais regardé de générateurs. Ils semblent effrayants (parce que je les ai vus dans des modules qui étaient généralement pénibles à utiliser).
ChewyChunks

2

Utilisez intersectionouintersection_update

  • intersection :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    ans = sorted(set(a).intersection(set(xyz)))
  • intersection_update :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    b = set(a)
    b.intersection_update(xyz)

    alors best ta réponse


2

J'ai aimé la réponse d'Alex , car un filtre est exactement un s'il est appliqué à une liste, donc si vous voulez explorer un sous-ensemble d'une liste en fonction d'une condition, cela semble être le moyen le plus naturel

mylist = [1,2,3,4,5]
another_list = [2,3,4]

wanted = lambda x:x in another_list

for x in filter(wanted, mylist):
    print(x)

cette méthode est utile pour la séparation des préoccupations, si la fonction de condition change, le seul code à manipuler est la fonction elle-même

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

for x in filter(wanted, mylist):
    print(x)

La méthode du générateur semble meilleure lorsque vous ne voulez pas de membres de la liste, mais une modification desdits membres, qui semble plus adaptée à un générateur

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.5 for x in mylist if wanted(x))

for x in generator:
    print(x)

De plus, les filtres fonctionnent avec des générateurs, bien que dans ce cas ce ne soit pas efficace

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.9 for x in mylist)

for x in filter(wanted, generator):
    print(x)

Mais bien sûr, ce serait quand même bien d'écrire comme ceci:

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

# for x in filter(wanted, mylist):
for x in mylist if wanted(x):
    print(x)

0

Un moyen simple de trouver des éléments communs uniques des listes a et b:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
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.