Pergunta

Quando você deve usar expressões geradoras e quando deve usar compreensões de lista em Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]
Foi útil?

Solução

A resposta de John é boa (a compreensão da lista é melhor quando você deseja iterar algo várias vezes).No entanto, também é importante notar que você deve usar uma lista se quiser usar qualquer um dos métodos de lista.Por exemplo, o código a seguir não funcionará:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Basicamente, use uma expressão geradora se tudo o que você estiver fazendo for iterar uma vez.Se você deseja armazenar e usar os resultados gerados, provavelmente será melhor compreender a lista.

Como o desempenho é o motivo mais comum para escolher um em vez de outro, meu conselho é não se preocupar com isso e apenas escolher um;se você achar que seu programa está sendo executado muito lentamente, então, e somente então, você deverá voltar e se preocupar em ajustar seu código.

Outras dicas

Iterando sobre o expressão geradora ou o compreensão da lista fará a mesma coisa.No entanto, o compreensão da lista criará a lista inteira na memória primeiro enquanto o expressão geradora criará os itens dinamicamente, para que você possa usá-lo em sequências muito grandes (e também infinitas!).

Use compreensões de lista quando o resultado precisar ser iterado várias vezes ou quando a velocidade for fundamental.Use expressões geradoras onde o intervalo for grande ou infinito.

O ponto importante é que a compreensão da lista cria uma nova lista.O gerador cria um objeto iterável que "filtrará" o material de origem dinamicamente à medida que você consome os bits.

Imagine que você tem um arquivo de log de 2 TB chamado "hugefile.txt" e deseja o conteúdo e o comprimento de todas as linhas que começam com a palavra "ENTRY".

Então você tenta começar escrevendo uma compreensão de lista:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Isso absorve todo o arquivo, processa cada linha e armazena as linhas correspondentes em seu array.Essa matriz poderia, portanto, conter até 2 TB de conteúdo.Isso é muita RAM e provavelmente não é prático para seus propósitos.

Então, em vez disso, podemos usar um gerador para aplicar um “filtro” ao nosso conteúdo.Nenhum dado é realmente lido até começarmos a iterar o resultado.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Nem mesmo uma única linha foi lida do nosso arquivo ainda.Na verdade, digamos que queremos filtrar ainda mais nosso resultado:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Ainda nada foi lido, mas especificamos agora dois geradores que agirão sobre nossos dados como desejarmos.

Vamos escrever nossas linhas filtradas em outro arquivo:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Agora lemos o arquivo de entrada.Como nosso for loop continua a solicitar linhas adicionais, o long_entries gerador demanda linhas do entry_lines gerador, retornando apenas aqueles cujo comprimento seja superior a 80 caracteres.E por sua vez, o entry_lines gerador solicita linhas (filtradas conforme indicado) do logfile iterador, que por sua vez lê o arquivo.

Portanto, em vez de "enviar" dados para sua função de saída na forma de uma lista totalmente preenchida, você está dando à função de saída uma maneira de "extrair" dados somente quando necessário.No nosso caso, isto é muito mais eficiente, mas não tão flexível.Os geradores são de sentido único, de passagem única;os dados do arquivo de log que lemos são imediatamente descartados, portanto não podemos voltar para uma linha anterior.Por outro lado, não precisamos nos preocupar em manter os dados por perto quando terminarmos de usá-los.

A vantagem de uma expressão geradora é que ela usa menos memória, pois não constrói a lista inteira de uma vez.As expressões geradoras são melhor usadas quando a lista é um intermediário, como ao somar os resultados ou criar um ditado a partir dos resultados.

Por exemplo:

sum(x*2 for x in xrange(256))

dict( ((k, some_func(k) for k in some_list_of_keys) )

A vantagem é que a lista não é gerada completamente e, portanto, pouca memória é usada (e também deve ser mais rápida)

Você deve, entretanto, usar compreensões de lista quando o produto final desejado for uma lista.Você não vai salvar nenhuma memória usando expressões geradoras, pois deseja a lista gerada.Você também obtém a vantagem de poder usar qualquer uma das funções da lista, como ordenada ou invertida.

Por exemplo:

reversed( [x*2 for x in xrange(256)] )

Ao criar um gerador a partir de um objeto mutável (como uma lista), esteja ciente de que o gerador será avaliado no estado da lista no momento do uso do gerador, não no momento da criação do gerador:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Se houver alguma chance de sua lista ser modificada (ou de um objeto mutável dentro dessa lista), mas você precisar do estado na criação do gerador, precisará usar uma compreensão de lista.

estou usando o Módulo Hadoop Mincemeat.Acho que este é um ótimo exemplo para observar:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Aqui, o gerador obtém números de um arquivo de texto (de até 15 GB) e aplica matemática simples a esses números usando a redução de mapa do Hadoop.Se eu não tivesse usado a função de rendimento, mas sim uma compreensão de lista, levaria muito mais tempo para calcular as somas e a média (sem falar na complexidade do espaço).

O Hadoop é um ótimo exemplo de aproveitamento de todas as vantagens dos Geradores.

Às vezes você pode escapar impune camiseta função de ferramentas iterativas, ele retorna vários iteradores para o mesmo gerador que podem ser usados ​​independentemente.

que tal usar [(exp for x in iter)] para obter o bem de ambos.Desempenho da compreensão do gerador, bem como métodos de lista

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top