¿Es Pythonic usar listas por comprensión solo para efectos secundarios?

108

Piense en una función a la que estoy llamando por sus efectos secundarios, no por devolver valores (como imprimir en pantalla, actualizar GUI, imprimir en un archivo, etc.).

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

Ahora, ¿es Pythonic usar listas por comprensión para llamar a esta función?

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

Tenga en cuenta que no guardo la lista en ningún lado

O debería llamar a esta función así:

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

¿Cuál es mejor y por qué?

sinan
fuente
6
esto es límite, pero probablemente obtendrá más oposición que apoyo. Voy a sentarme en este: ^)
jcomeau_ictx
6
Esta es una elección sencilla. La legibilidad cuenta, hágalo de la segunda manera. Si no puede colocar 2 líneas adicionales en su pantalla, obtenga un monitor más grande :)
John La Rooy
1
La comprensión de la lista no es pitónica ya que viola "lo explícito es mejor que lo implícito": está ocultando un bucle en una construcción diferente.
Fred Foo
3
@larsmans: ¡si tan solo GvR se hubiera dado cuenta de eso cuando introdujo listas por comprensión en primer lugar!
Steve Jessop
2
@larsmans, Steve Jessop, creo que es incorrecto concebir la comprensión de una lista como un bucle. Bien puede implementarse como un bucle, pero el objetivo de construcciones como esta es operar en datos agregados de una manera funcional y (conceptualmente) paralela. Si hay un problema con la sintaxis, es que for ... inse usa en ambos casos, ¡lo que lleva a preguntas como esta!
remitente

Respuestas:

84

Es muy anti-Pythonic hacerlo, y cualquier Pythonista experimentado te dará un infierno. La lista intermedia se desecha una vez creada y, potencialmente, podría ser muy, muy grande y, por lo tanto, costosa de crear.

Ignacio Vázquez-Abrams
fuente
5
Entonces, ¿cuál sería una forma más pitónica?
Joachim Sauer
6
El que no lleva la lista; es decir, alguna variante de la segunda forma (se sabe que he usado un genex forantes, para deshacerme del if).
Ignacio Vazquez-Abrams
6
@Joachim Sauer: Ejemplo 2 anterior. Un ciclo apropiado, explícito, sin comprensión de listas. Explícito. Claro. Obvio.
S.Lott
31

No debe utilizar una lista de comprensión porque, como ha dicho la gente, creará una gran lista temporal que no necesita. Los dos métodos siguientes son equivalentes:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

con la definición de consumede la itertoolspágina de manual:

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)

Por supuesto, este último es más claro y más fácil de entender.

Katriel
fuente
@Paul: Creo que debería serlo. Y, de hecho, puede, aunque mappuede que no sea tan intuitivo si no ha realizado programación funcional antes.
Katriel
4
No estoy seguro de que esto sea especialmente idiomático. No hay ninguna ventaja sobre el uso del bucle explícito.
Marcin
1
La solución esconsume = collections.deque(maxlen=0).extend
PaulMcG
24

Las listas por comprensión son para crear listas. Y a menos que en realidad está creando una lista, usted debe no utilizar listas por comprensión.

Entonces obtuve la segunda opción, simplemente iterando sobre la lista y luego llamar a la función cuando se apliquen las condiciones.

Ikke
fuente
6
Iría aún más lejos y afirmaría que los efectos secundarios dentro de la comprensión de una lista son inusuales, inesperados y, por lo tanto, malvados, incluso si está utilizando la lista resultante cuando haya terminado.
Mark Ransom
11

Segundo es mejor.

Piense en la persona que necesitaría comprender su código. Puedes conseguir mal karma fácilmente con el primero :)

Puede ir al medio entre los dos usando filter (). Considere el ejemplo:

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)
user237419
fuente
10
Su lambda está mucho mejor escrito como lambda x : x > 3.
PaulMcG
Ni siquiera necesitas filtro. Sólo hay que poner una expresión generadora de parens aquí: for el in (x for x in y if x > 3):. ely xpuede tener el mismo nombre, pero eso podría confundir a la gente.
Omnifarious
3

Depende de tu objetivo.

Si está intentando realizar alguna operación en cada objeto de una lista, debe adoptar el segundo enfoque.

Si está intentando generar una lista a partir de otra lista, puede utilizar la comprensión de listas.

Explícito es mejor que implícito. Mejor es simple que complejo. (Python Zen)

rubayeet
fuente
0

Tu puedes hacer

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

pero no es muy bonito.

sigs
fuente
-1

Usar una lista de comprensión para sus efectos secundarios es feo, no pitónico, ineficiente y yo no lo haría. En su forlugar, usaría un bucle, porque un forbucle indica un estilo de procedimiento en el que los efectos secundarios son importantes.

Pero, si insiste absolutamente en usar una comprensión de lista para sus efectos secundarios, debe evitar la ineficiencia usando una expresión generadora. Si insiste absolutamente en este estilo, haga uno de estos dos:

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

o:

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

Estas son expresiones generadoras y no generan una lista aleatoria que se descarta. Creo que el allformulario es quizás un poco más claro, aunque creo que ambos son confusos y no deberían usarse.

Creo que esto es feo y en realidad no lo haría en código. Pero si insistes en implementar tus bucles de esta manera, así es como lo haría yo.

Tiendo a sentir que las listas por comprensión y las similares deberían indicar un intento de usar algo que al menos se parezca ligeramente a un estilo funcional. Poner cosas con efectos secundarios que rompan esa suposición hará que las personas tengan que leer su código con más cuidado, y creo que eso es algo malo.

De todo género
fuente
¿Qué pasa si fun_with_side_effectsdevuelve True?
Katriel
7
Creo que esta cura es peor que la enfermedad: itertools.consume es mucho más limpio.
PaulMcG
@PaulMcG - itertools.consumeya no existe, probablemente porque usar comprensiones con efectos secundarios es feo.
Omnifarious
1
Resulta que estaba equivocado y nunca existió como método en stdlib. Que es una receta en la documentación itertools: docs.python.org/3/library/...
PaulMcG