¿Es Pythonic usar la lista de comprensión solo para efectos secundarios?

Piense en una función que estoy solicitando efectos secundarios, no valores de retorno (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 la comprensión de listas 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 lugar

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

Es muy anti-pythonico hacerlo, y cualquier Pythonista experimentado te dará el infierno. La lista intermedia se desecha después de su creación, y potencialmente podría ser muy, muy grande y, por lo tanto, costosa de crear.

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

 consume(side_effects(x) for x in xs) for x in xs: side_effects(x) 

Con la definición de consume de la página del manual de itertools :

 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.

Las comprensiones de listas son para crear listas. Y a menos que realmente esté creando una lista, no debe usar las comprensiones de listas.

Así que obtuve la segunda opción, solo iterando sobre la lista y luego llamar a la función cuando se apliquen las condiciones.

Segundo es mejor

Piense en la persona que necesitaría entender su código. Puedes obtener mal karma fácilmente con el primero 🙂

Podría ir a la mitad entre los dos usando filter (). Considera 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) 

Depende de tu objective.

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

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

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

Tu puedes hacer

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

pero no es muy bonito

El uso de una lista de comprensión para sus efectos secundarios es feo, no pythonico, ineficiente, y no lo haría. Yo usaría un bucle for lugar, porque un bucle for señala un estilo de procedimiento en el que los efectos secundarios son importantes.

Pero, si insistes absolutamente en usar una lista de comprensión para sus efectos secundarios, debes evitar la ineficiencia usando una expresión generadora. Si insistes absolutamente en este estilo, haz 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 elimine. Creo que all forma es quizás un poco más clara, aunque creo que ambas son confusas 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.

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