Pergunta

Este é um pouco o inverso do O que você pode usar as funções de gerador de Python para :? geradores pitão, gerador de expressões, e o módulo itertools são alguns dos meus recursos favoritos de python estes dias. Eles são especialmente úteis quando a criação de cadeias de operações para executar em uma grande pilha de dados - muitas vezes eu usá-los ao processar arquivos DSV

.

Então, quando é não um bom momento para usar um gerador, ou um gerador de expressão, ou uma função itertools?

  • Quando devo preferem zip() sobre itertools.izip(), ou
  • range() sobre xrange(), ou
  • [x for x in foo] sobre (x for x in foo)?

Obviamente, nós eventualmente precisar de "resolver" um gerador em dados reais, geralmente através da criação de uma lista ou iteração sobre ela com um laço não-gerador. Às vezes só precisamos de saber o comprimento. Este não é o que eu estou pedindo.

Nós usamos geradores de modo a que não estamos atribuindo novas listas em memória para dados provisórios. Isto é especialmente faz sentido para grandes conjuntos de dados. Faz sentido para pequenos conjuntos de dados também? Existe uma memória notável / cpu trade-off?

Estou especialmente interessado se alguém fez alguma profiling sobre este assunto, à luz da discussão de compreensão da lista mapa de desempenho vs. () e filter () . ( alt ligação )

Foi útil?

Solução

Use uma lista em vez de um gerador quando:

1) Você precisa acessar os dados múltiplas vezes (ou seja, cache de resultados em vez de refazer os cálculos):

for i in outer:           # used once, okay to be a generator or return a list
    for j in inner:       # used multiple times, reusing a list is better
         ...

2) você precisa de acesso aleatório (ou qualquer acesso diferente de ordem sequencial para a frente):

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) Você precisa join strings (que requer duas passagens sobre os dados):

s = ''.join(data)                # lists are faster than generators in this use case

4) Você está usando PyPy que às vezes não pode otimizar o código gerador, tanto quanto pode com chamadas de funções normais e lista de manipulações.

Outras dicas

Em geral, não use um gerador quando você precisa de operações de lista, assim como LEN (), inverteu (), e assim por diante.

Também pode haver momentos em que você não quer avaliação preguiçosa (por exemplo, para fazer todo o cálculo na frente para que você pode liberar um recurso). Nesse caso, uma expressão lista poderia ser melhor.

perfil, do perfil, perfil.

perfil de seu código é a única maneira de saber se o que você está fazendo tem qualquer efeito.

A maioria dos usos de xrange, geradores, etc são mais de tamanho estático, pequenos conjuntos de dados. É só quando você começa a grandes conjuntos de dados que realmente faz a diferença. range () vs. xrange () é na maior parte apenas uma questão de fazer o olhar código um pouco pouquinho mais feio, e não perder nada, e talvez ganhar algo.

perfil, do perfil, perfil.

Você nunca deve favorecer zip sobre izip , range sobre xrange, ou lista mais compreensões gerador. Em Python 3.0 range tem xrange-like semântica e zip tem izip-like semântica.

compreensões lista são realmente mais clara como list(frob(x) for x in foo) para aqueles momentos em que você precisa de uma lista real.

Como você menciona, "Isto é especialmente faz sentido para grandes conjuntos de dados", eu acho que isso responde sua pergunta.

Se o seu não bater nos muros, em termos de performance, você pode ainda ficar com listas e funções padrão. Então, quando você tiver problemas com o desempenho fazer a troca.

Como mencionado por @ u0b34a0f6ae nos comentários, no entanto, usando geradores no início pode tornar mais fácil para você escala para conjuntos de dados maiores.

Com relação ao desempenho: se estiver usando o psyco, listas podem ser um pouco mais rápido do que geradores. No exemplo abaixo, as listas são quase 50% mais rápido quando se usa psyco.full ()

import psyco
import time
import cStringIO

def time_func(func):
    """The amount of time it requires func to run"""
    start = time.clock()
    func()
    return time.clock() - start

def fizzbuzz(num):
    """That algorithm we all know and love"""
    if not num % 3 and not num % 5:
        return "%d fizz buzz" % num
    elif not num % 3:
        return "%d fizz" % num
    elif not num % 5:
        return "%d buzz" % num
    return None

def with_list(num):
    """Try getting fizzbuzz with a list comprehension and range"""
    out = cStringIO.StringIO()
    for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
        print >> out, fibby
    return out.getvalue()

def with_genx(num):
    """Try getting fizzbuzz with generator expression and xrange"""
    out = cStringIO.StringIO()
    for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
        print >> out, fibby
    return out.getvalue()

def main():
    """
    Test speed of generator expressions versus list comprehensions,
    with and without psyco.
    """

    #our variables
    nums = [10000, 100000]
    funcs = [with_list, with_genx]

    #  try without psyco 1st
    print "without psyco"
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

    #  now with psyco
    print "with psyco"
    psyco.full()
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

if __name__ == "__main__":
    main()

Resultado:

without psyco
  number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds

  number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds

with psyco
  number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds

  number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds

Quanto desempenho está em causa, não posso pensar em todas as vezes que você gostaria de usar uma lista de mais de um gerador.

Eu nunca encontrei uma situação onde os geradores iria dificultar o que você está tentando fazer. Há, no entanto, a abundância de casos em que usando geradores não iria ajudá-lo mais do que não usá-los.

Por exemplo:

sorted(xrange(5))

não oferece qualquer melhoria ao longo:

sorted(range(5))

Você deve preferir compreensões lista se você precisa manter os valores em torno de outra coisa mais tarde e do tamanho do seu conjunto não é muito grande.

Por exemplo: você está criando uma lista que você irá repetir várias vezes mais tarde em seu programa.

Até certo ponto, você pode pensar de geradores como um substituto para iteração (loops) compreensões lista vs. como um tipo de inicialização estrutura de dados. Se você quiser manter a estrutura de dados, em seguida, lista Utilize compreensões.

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