É Pythônico usar compreensões de lista apenas para efeitos colaterais?


108

Pense em uma função que estou chamando por seus efeitos colaterais, não por valores de retorno (como imprimir na tela, atualizar a GUI, imprimir em um arquivo etc.).

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

Agora, é Pythônico usar compreensões de lista para chamar esta função:

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

Observe que eu não salvo a lista em nenhum lugar

Ou devo chamar essa função assim:

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

Qual é melhor e por que?


6
isso é limítrofe, mas você provavelmente terá mais oposição do que apoio. Vou ficar de fora: ^)
jcomeau_ictx

6
Esta é uma escolha fácil. A legibilidade conta - faça da segunda maneira. Se você não consegue colocar 2 linhas extras na tela, compre um monitor maior :)
John La Rooy,

1
A compreensão da lista não é sincrônica, pois viola "explícito é melhor do que implícito" - você está escondendo um loop em uma construção diferente.
Fred Foo

3
@larsmans: se ao menos GvR tivesse percebido isso quando introduziu as compreensões de lista em primeiro lugar!
Steve Jessop,

2
@larsmans, Steve Jessop, acho incorreto conceber uma compreensão de lista como um loop. Pode muito bem ser implementado como um loop, mas o objetivo de construções como essa é operar em dados agregados de maneira funcional e (conceitualmente) paralela. Se houver um problema com a sintaxe, é a que for ... iné usada em ambos os casos - levando a questões como esta!
remetente

Respostas:


84

É muito anti-pitônico fazer isso, e qualquer pythonista experiente vai te dar uma bronca por isso. A lista intermediária é descartada após ser criada e pode ser potencialmente muito grande e, portanto, cara de criar.


5
Então, qual seria uma forma mais pítônica?
Joachim Sauer

6
Aquele que não mantém a lista; ou seja, alguma variante da segunda forma (sou conhecido por usar um genex forantes, para me livrar do if).
Ignacio Vazquez-Abrams,

6
@Joachim Sauer: Exemplo 2 acima. Um loop de compreensão adequado, explícito e não relacionado à lista. Explícito. Claro. Óbvio.
S.Lott

31

Você não deve usar uma compreensão de lista , porque como as pessoas disseram, construirá uma grande lista temporária de que você não precisa. Os dois métodos a seguir são equivalentes:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

com a definição de consumeda 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)

Claro, o último é mais claro e fácil de entender.


@Paul: Acho que deveria. E, de fato, você pode, embora mappossa não ser tão intuitivo se não houver programação funcional antes.
Katriel,

4
Não tenho certeza se isso é especialmente idiomático. Não há nenhuma vantagem sobre o uso do loop explícito.
Marcin

1
A solução éconsume = collections.deque(maxlen=0).extend
PaulMcG

24

As compreensões de lista servem para criar listas. E a menos que você esteja realmente criando uma lista, você não deve usar compreensões lista.

Portanto, eu escolheria a segunda opção, apenas iterar a lista e, em seguida, chamar a função quando as condições se aplicassem.


6
Eu iria ainda mais longe e afirmaria que os efeitos colaterais dentro da compreensão de uma lista são incomuns, inesperados e, portanto, maléficos, mesmo se você estiver usando a lista resultante quando terminar.
Mark Ransom

11

O segundo é melhor.

Pense na pessoa que precisa entender seu código. Você pode obter carma ruim facilmente com o primeiro :)

Você pode ir no meio dos dois usando filter (). Considere o exemplo:

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
Seu lambda é muito melhor escrito como lambda x : x > 3.
PaulMcG

Você nem precisa de filtro. Basta colocar um gerador de expressão em parênteses aqui: for el in (x for x in y if x > 3):. ele xpode ter o mesmo nome, mas isso pode confundir as pessoas.
Onifário

3

Depende do seu objetivo.

Se você está tentando fazer alguma operação em cada objeto em uma lista, a segunda abordagem deve ser adotada.

Se você está tentando gerar uma lista de outra lista, você pode usar a compreensão de lista.

Explícito é melhor do que implícito. Simples é melhor que complexo. (Python Zen)


0

Você pode fazer

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

mas não é muito bonito.


-1

Usar uma compreensão de lista para seus efeitos colaterais é feio, não Pythônico, ineficiente e eu não faria isso. Eu usaria um forloop em vez disso, porque um forloop sinaliza um estilo de procedimento no qual os efeitos colaterais são importantes.

Mas, se você absolutamente insiste em usar uma compreensão de lista para seus efeitos colaterais, deve evitar a ineficiência usando uma expressão geradora. Se você insiste absolutamente neste estilo, escolha um destes dois:

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

Essas são expressões geradoras e não geram uma lista aleatória que é descartada. Eu acho que oall forma talvez seja um pouco mais clara, embora ache que ambas são confusas e não devam ser usadas.

Eu acho isso feio e eu não faria isso em código. Mas se você insiste em implementar seus loops dessa maneira, é assim que eu faria.

Tenho a tendência de achar que as compreensões de lista e de sua laia devem sinalizar uma tentativa de usar algo que se assemelhe pelo menos um pouco a um estilo funcional. Colocar coisas com efeitos colaterais que quebrem essa suposição fará com que as pessoas tenham que ler seu código com mais cuidado, e acho isso uma coisa ruim.


E se fun_with_side_effectsretornar True?
Katriel,

7
Acho que essa cura é pior do que a doença - os itertools.consume são muito mais limpos.
PaulMcG

@PaulMcG - itertools.consumenão existe mais, provavelmente porque usar compreensões com efeitos colaterais é feio.
Onifário 01 de

1
Acontece que eu estava enganado e isso nunca existiu como um método no stdlib. Ele é uma receita nos docs itertools: docs.python.org/3/library/...
PaulMcG
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.