Use caso para aninhados / múltiplas compreensões lista ou gerador de expressões. Quando é que é mais elegante?

StackOverflow https://stackoverflow.com/questions/648675

Pergunta

Eu vejo esse tipo de coisa às vezes:

(k for k in (j for j in (i for i in xrange(10))))

Agora, isso realmente curvas meu cérebro, e eu preferia que não foi apresentado desta forma.

Existem casos de uso, ou exemplos de ter usado essas expressões aninhadas onde era mais elegante e mais legível do que se tivesse sido um loop aninhado?

Editar: Obrigado pelas exemplos de maneiras de simplificar isso. Na verdade não é o que eu pedi, eu queria saber se havia alguma vezes quando era elegante.

Foi útil?

Solução

Verifique PEP 202 que foi onde sintaxe compreensões lista foi apresentada ao linguagem.

Para compreender o seu exemplo, há uma regra simples do próprio Guido:

  • A forma [... para x ... para y ...] ninhos, com o último índice variando mais rápido, assim como aninhados para loops.

Também a partir de PEP 202, que serve para responder à sua pergunta:

Rationale
    List comprehensions provide a more concise way to create lists in
    situations where map() and filter() and/or nested loops would
    currently be used.

Se você tivesse uma situação como essa, você pode encontrá-lo para ser mais elegante. IMHO, no entanto, várias compreensões lista aninhados pode ser menos claro em seu código de nested loops, desde laços for são facilmente analisado visualmente.

Outras dicas

Se você está preocupado com muita complexidade em uma linha, você pode dividi-lo:

(k for k in 
    (j for j in 
        (i for i in xrange(10))))

Eu sempre achei continuação de linha para olhar um pouco estranho em Python, mas isto faz com que seja mais fácil ver o que cada um está looping sobre. Desde uma atribuição adicional / pesquisa não vai fazer ou quebrar qualquer coisa, você também pode escrever assim:

gen1 = (i for i in xrange(10))
gen2 = (j for j in gen1)
gen3 = (k for k in gen2)

Na prática, eu não acho que eu já aninhados uma compreensão mais de 2 de profundidade, e nesse ponto ainda era bastante fácil de entender.

No caso do seu exemplo, eu provavelmente iria escrevê-lo como:

foos = (i for i in xrange(10))
bars = (j for j in foos)
bazs = (k for k in bars)

Dada nomes mais descritivos, penso que este seria provavelmente muito claro, e eu não posso imaginar que haja qualquer diferença de desempenho mensurável.

Talvez você esteja pensando mais de expressões como:

(x for x in xs for xs in ys for ys in lst)

- na verdade, isso não é mesmo válido. Você tem que colocar as coisas em outra ordem:

(x for ys in lst for xs in ys for x in xs)

Eu poderia escrever isso como uma maneira rápida de achatamento uma lista, mas em geral eu acho que você está escrita: o tempo que você economizar por datilografar menos é geralmente equilibrado pelo tempo extra que você gasta obter o direito de expressão gerador

Uma vez que eles são expressões gerador, você pode vincular cada um para o seu próprio nome para torná-lo mais legível, sem qualquer alteração no desempenho. Mudá-lo para um loop aninhado provavelmente seria prejudicial para o desempenho.

irange = (i for i in xrange(10))
jrange = (j for j in irange)
krange = (k for k in jrange)

Realmente não importa qual você escolher, acho que o exemplo de multi-line é mais legível, em geral.

Advertência:. Elegância é em parte uma questão de gosto

compreensões lista para nunca mais são clara que o correspondente expandida para loop. Para loops são também mais poderoso do que compreensões lista. Então, por que usá-los em tudo?

O que compreensões lista são é concisa -. Eles permitem que você faça alguma coisa em uma única linha

A hora de usar uma compreensão da lista é quando você precisa de uma determinada lista, ele pode ser criado na mosca com bastante facilidade, e você não quer ou precisa de objetos intermediários que penduram ao redor. Isso pode acontecer quando você precisa para embalar alguns objetos no escopo atual em um único objeto que você pode alimentar em uma função, como a seguir:

list1 = ['foo', 'bar']
list2 = ['-ness', '-ity']
return filterRealWords([str1+str2 for str1 in list1 for str2 in list2])

Este código é tão legível do que a versão expandida, mas é muito mais curto. Evita a criação / nomear um objeto que é usado apenas uma vez no escopo atual, que é sem dúvida mais elegante.

A expressão:

(k for k in (j for j in (i for i in xrange(10))))

é equivalente a:

(i for i in xrange(10))

que é quase o mesmo:

xrange(10)

A última variante é mais elegante do que o primeiro.

Eu acho que pode ser útil e elegante em situações como estas, onde você tem um código como este:

output = []
for item in list:
    if item >= 1:
        new = item += 1
        output.append(new)

Você pode torná-lo um one-liner como esta:

output = [item += 1 for item in list if item >= 1]
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top