Forma pitónica de combinar el bucle FOR y la declaración IF

266

Sé cómo usar tanto para bucles como para declaraciones en líneas separadas, como:

>>> 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

Y sé que puedo usar una comprensión de lista para combinarlos cuando las declaraciones son simples, como:

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

Pero lo que no puedo encontrar es un buen ejemplo en cualquier lugar (para copiar y aprender) que demuestre un conjunto complejo de comandos (no solo "imprimir x") que ocurren después de una combinación de un bucle for y algunas declaraciones if. Algo que esperaría se ve así:

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

¿No es así como se supone que funciona Python?

ChewyChunks
fuente
23
Así es ... no compliques demasiado las cosas tratando de simplificarlas. Pythonic no significa evitar cada forciclo explícito y ifdeclaración.
Felix Kling
2
Puede usar la lista generada en su comprensión de la lista en un bucle for. Eso se parecería un poco a tu último ejemplo.
Jacob
Entonces, para comenzar con el procesamiento, ¿cuál es la forma más rápida de combinar un ciclo for con una declaración if, si la declaración if excluye valores que ya se han hecho coincidir y la lista crece continuamente durante la iteración del ciclo for?
ChewyChunks
3
@Chewy, las estructuras de datos adecuadas harán que el código sea más rápido, no el azúcar sintáctico. Por ejemplo, x in aes lento si aes una lista.
Nick Dandoulakis
1
Este es Python, un lenguaje interpretado; ¿Por qué alguien está discutiendo qué tan rápido es el código?
ArtOfWarfare

Respuestas:

323

Puede usar expresiones generadoras como esta:

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

for x in gen:
    print x
Kugel
fuente
1
gen = (y for (x,y) in enumerate(xyz) if x not in a)devuelve >>> 12cuando escribo for x in gen: print x, entonces, ¿por qué el comportamiento inesperado con enumerate?
ChewyChunks
9
Posible, pero no más bonito que el original para y si bloques.
Mike Graham
1
@ChewyChunks. Eso funcionaría, pero la llamada a enumerar es redundante.
Johnsyweb
132
Realmente extraño en Python poder decirfor x in xyz if x:
bgusach
10
for x in (x for x in xyz if x not in a):funciona para mí, pero por qué no deberías poder hacerlo for x in xyz if x not in a:, no estoy seguro ...
Matt Wenham
34

Según The Zen of Python (si se pregunta si su código es "Pythonic", ese es el lugar para ir):

  • Hermoso es mejor que feo.
  • Explícito es mejor que implícito.
  • Lo simple es mejor que lo complejo.
  • Plano es mejor que anidado.
  • La legibilidad cuenta.

La forma pitónica de obtener el de dos s es:sorted intersectionset

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

O aquellos elementos que están xyzpero no en a:

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

Pero para un ciclo más complicado, es posible que desee aplanarlo iterando sobre una expresión generadora bien nombrada y / o llamando a una función bien nombrada. Tratar de encajar todo en una línea rara vez es "Pythonic".


Actualice los siguientes comentarios adicionales sobre su pregunta y la respuesta aceptada

No estoy seguro de lo que está tratando de hacer enumerate, pero si aes un diccionario, probablemente quiera usar las teclas, como esta:

>>> 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
Johnsyweb
fuente
Parece que de los comentarios a continuación, debería estar estudiando sobre generadores. Nunca los he usado. Gracias. ¿Es un generador más rápido que la combinación equivalente de declaraciones FOR e IF? También he usado conjuntos, pero a veces los elementos redundantes en una lista son información que no puedo descartar.
ChewyChunks
@ChewyChunks: ¡Los generadores no son la única forma de ser Pythonic!
Johnsyweb
3
@Johnsyweb, si vas a citar el Zen de Python: "Debería haber una, y preferiblemente solo una, forma obvia de hacerlo".
Wooble
@Wooble: Debería. ¡Cité esa sección en mi respuesta a otra pregunta al mismo tiempo!
Johnsyweb
18

Personalmente, creo que esta es la versión más bonita:

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

Editar

Si está muy interesado en evitar el uso de lambda, puede usar la aplicación de función parcial y usar el módulo de operador (que proporciona las funciones de la mayoría de los operadores).

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)))
Alex
fuente
44
filter(a.__contains__, xyz). Por lo general, cuando las personas usan lambda, realmente necesitan algo mucho más simple.
Veky
Creo que malinterpretaste algo. __contains__es un método como cualquier otro, solo que es un método especial , lo que significa que puede ser llamado indirectamente por un operador ( inen este caso). Pero también se puede llamar directamente, es parte de la API pública. Los nombres privados se definen específicamente como que tienen como máximo un guión bajo final, para proporcionar una excepción para los nombres de métodos especiales, y están sujetos a cambios de nombre cuando léxicamente en los ámbitos de clase. Ver docs.python.org/3/reference/datamodel.html#specialnames y docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky
Ciertamente está bien, pero dos importaciones solo para poder referirnos a un método que es accesible usando solo un atributo parece extraño (los operadores generalmente se usan cuando el despacho doble es esencial, pero inse despacha individualmente con el operando correcto). Además, tenga en cuenta que operatortambién exporta el containsmétodo bajo el nombre __contains__, por lo que seguramente no es un nombre privado. Creo que solo tendrás que aprender a vivir con el hecho de que no todos los guiones bajos dobles significan "mantente alejado". : -]
Veky
Creo que sus lambdanecesidades de reparación para incluir not: lambda w: not w in a, xyz
Java
El filtro parece más elegante, especialmente para condiciones complejas que se convertirían en funciones definidas en lugar de lambdas, tal vez nombrar la función lambda agregaría algo de legibilidad. El generador parece mejor cuando los elementos iterados son alguna modificación en los elementos de la lista
Khanis Rok
16

La siguiente es una simplificación / una línea de la respuesta aceptada:

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

Tenga en cuenta que generatorse mantuvo en línea . Esto se probó python2.7y python3.6 (observe los padres en el print;))

javadba
fuente
10

Probablemente usaría:

for x in xyz: 
    if x not in a:
        print x...
Wim Feijen
fuente
@KirillTitov Sí, Python es un lenguaje fundamentalmente no funcional (esta es una codificación puramente imperativa, y estoy de acuerdo con el autor de esta respuesta en que es la forma en que Python está configurado para ser escrito. Intentar utilizar funciones conduce a una lectura deficiente o no pythonicresultados. Puedo codificar funcionalmente en cualquier otro idioma que use (scala, kotlin, javascript, R, swift, ..) pero difícil / incómodo 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])
Kracekumar
fuente
Muy Zen, @lazyr, pero no me ayudaría a mejorar un bloque de código complejo que depende de iterar a través de una lista e ignorar elementos coincidentes en otra lista. ¿Es más rápido tratar la primera lista como un conjunto y comparar unión / diferencia con una segunda lista creciente de "ignorar"?
ChewyChunks
Prueba estoimport 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 alguna de las listas cambia durante la iteración, probablemente será más rápido verificar cada elemento con la lista de ignorados, excepto que debe hacer que sea un conjunto de ignorados. La comprobación de la pertenencia a conjuntos es muy rápido: if x in ignore: ....
Lauritz V. Thaulow
@lazyr Acabo de reescribir mi código usando un conjunto de ignorar sobre una lista de ignorar. Parece procesar el tiempo mucho más lento. (Para ser justos estaba comparando el uso de if set(a) - set(ignore) == set([]):lo que tal vez es por eso que fue mucho más lento que el control de la pertenencia Voy a probar esto de nuevo en el futuro en un ejemplo mucho más simple que lo que estoy escribiendo..
ChewyChunks
5

También puede usar generadores , si las expresiones generadoras se vuelven demasiado complicadas o complejas:

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

for x in gen():
    print x
Lauritz V. Thaulow
fuente
Esto es un poco más útil para mí. Nunca he visto generadores. Suenan atemorizantes (porque los vi en módulos que generalmente eran difíciles de usar).
ChewyChunks
2

Use intersectionointersection_update

  • intersección :

    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)

    entonces bes tu respuesta

Chung-Yen Hung
fuente
2

Me gustó la respuesta de Alex , porque un filtro es exactamente si se aplica a una lista, por lo que si desea explorar un subconjunto de una lista dada una condición, esta parece ser la forma más natural

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)

Este método es útil para la separación de inquietudes, si la función de condición cambia, el único código para jugar es la función en sí

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

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

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

El método generador parece mejor cuando no desea miembros de la lista, pero una modificación de dichos miembros, que parece más adecuada para un generador

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)

Además, los filtros funcionan con generadores, aunque en este caso no es eficiente

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)

Pero, por supuesto, aún sería bueno escribir así:

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)
Khanis Rok
fuente
0

Una manera simple de encontrar elementos comunes únicos de las listas a y b:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
Peawormsworth
fuente