Generator Expressões vs. compreensão da lista

votos
317

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

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

# List comprehension
[x*2 for x in range(256)]
Publicado 06/09/2008 em 21:07
fonte usuário
Em outras línguas...                            


8 respostas

votos
225

A resposta de John é bom (que compreensões lista são melhores quando você quer iterar sobre algo várias vezes). No entanto, é importante notar também que você deve usar uma lista se você 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, usar uma expressão gerador se tudo que você está fazendo é repetindo uma vez. Se você deseja armazenar e utilizar os resultados gerados, então você é provavelmente melhor fora com uma compreensão da lista.

Como o desempenho é a razão mais comum para escolher um sobre o outro, o meu conselho é não se preocupar com isso e basta escolher um; Se você achar que seu programa está sendo executado muito lentamente, então e só então você deve voltar e se preocupar com ajustar seu código.

Respondeu 06/09/2008 em 21:54
fonte usuário

votos
140

Iteração sobre o gerador de expressão ou a compreensão da lista vai fazer a mesma coisa. No entanto, a compreensão da lista irá criar a lista inteira na memória primeiro, enquanto a expressão gerador irá criar os itens na mosca, assim que você é capaz de usá-lo para seqüências muito grandes (e também infinitas!).

Respondeu 06/09/2008 em 21:11
fonte usuário

votos
76

Use compreensões lista quando o resultado precisa ser iterado várias vezes, ou onde a velocidade é primordial. Use gerador de expressões, onde o intervalo é grande ou infinito.

Respondeu 06/09/2008 em 21:10
fonte usuário

votos
44

O ponto importante é que a compreensão da lista cria uma nova lista. O gerador cria um objeto iterável que irá "filtrar" o material de fonte on-the-fly como você consumir os bits.

Imagine que você tem um arquivo de log 2TB chamado "hugefile.txt", e deseja que o conteúdo ea duração para todas as linhas que começam com a palavra "ENTRADA".

Então você tente começar por escrever uma lista de compreensão:

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

Este slurps-se o arquivo inteiro, processa cada linha, e armazena as linhas correspondentes na sua matriz. Esta matriz pode, portanto, conter até 2TB de conteúdo. Isso é um monte de RAM, e provavelmente não é prático para seus propósitos.

Então, em vez disso, pode usar um gerador para aplicar um "filtro" para o nosso conteúdo. Nenhum dado é realmente ler até começarmos a iteração sobre 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 lido do nosso arquivo ainda. Na verdade, dizer que queremos filtrar nosso resultado ainda mais longe:

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

Ainda nada foi lido, mas nós especificado agora dois geradores que irão atuar em nossos dados como nós desejamos.

Permite escrever nossas linhas filtradas para outro arquivo:

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

Agora podemos ler o arquivo de entrada. Como nosso forciclo continua a solicitar linhas adicionais, o long_entriesgerador exige linhas do entry_linesgerador, retornando somente aqueles cujo comprimento é maior que 80 caracteres. E, por sua vez, o entry_linesgerador solicita linhas (filtrado como indicado) a partir do logfileiterator, que por sua vez lê o arquivo.

Então, ao invés de "empurrar" os dados à sua função de saída na forma de uma lista totalmente preenchida, você está dando a função de saída uma forma de "puxar" os dados apenas quando o seu necessários. Esta é, no nosso caso muito mais eficiente, mas não tão flexível. Geradores são uma forma, uma passagem; os dados do arquivo de log que li é imediatamente descartado, por isso não podemos voltar para uma linha anterior. Por outro lado, não precisa se preocupar em manter os dados em torno de uma vez que é feito com ele.

Respondeu 04/04/2014 em 10:14
fonte usuário

votos
40

O benefício de um gerador de expressão é que ele usa menos memória, uma vez que não construir a lista inteira de uma só vez. gerador de expressões são mais utilizados quando a lista é um intermediário, como soma dos resultados, ou a criação de um dicionário 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 não é que a lista não é completamente gerado e, portanto, pouca memória é usado (e também deve ser mais rápido)

Você deve, no entanto, usar compreensões lista quando o produto final desejado é uma lista. Você não está indo para salvar qualquer memeory utilizando gerador de expressões, uma vez que a lista gerada. Você também terá a vantagem de ser capaz de usar qualquer uma das funções de lista como ordenados ou revertida.

Por exemplo:

reversed( [x*2 for x in xrange(256)] )
Respondeu 10/10/2008 em 02:42
fonte usuário

votos
9

Ao criar um gerador de um objeto mutável (como uma lista) estar ciente de que o gerador vai ser avaliada sobre o estado da lista no momento de usar o gerador, e 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 qualquer chance de sua lista se modificado (ou um objeto mutável dentro dessa lista), mas você precisa do estado na criação do gerador que você precisa usar uma compreensão de lista em seu lugar.

Respondeu 12/03/2016 em 22:21
fonte usuário

votos
4

Eu estou usando o módulo Hadoop Mincemeat . Acho que este é um grande exemplo para tomar nota de:

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 fica números fora de um arquivo de texto (tão grande como 15GB) e aplica-se matemática simples sobre os números usando o Hadoop Map-Reduce. Se eu não tivesse utilizado a função de rendimento, mas sim uma compreensão da lista, que teria levado muito mais tempo calcular as somas e média (para não mencionar a complexidade espaço).

Hadoop é um grande exemplo para o uso de todas as vantagens de geradores.

Respondeu 04/01/2016 em 20:31
fonte usuário

votos
3

Às vezes você pode sair com o tee função de itertools , ele retorna vários iteradores para o mesmo gerador que pode ser usado de forma independente.

Respondeu 10/09/2008 em 01:58
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more