Em Python, há alguma diferença entre criar um objeto gerador por meio de uma expressão geradora e usar a instrução yield ?
Usando o rendimento :
def Generator(x, y):
for i in xrange(x):
for j in xrange(y):
yield(i, j)
Usando expressão geradora :
def Generator(x, y):
return ((i, j) for i in xrange(x) for j in xrange(y))
Ambas as funções retornam objetos geradores, que produzem tuplas, por exemplo (0,0), (0,1) etc.
Alguma vantagem de um ou de outro? Pensamentos?
Obrigado a todos! Há muitas informações úteis e referências adicionais nessas respostas!
python
python-3.x
generator
yield
cschol
fonte
fonte
Respostas:
Existem apenas pequenas diferenças entre os dois. Você pode usar o
dis
módulo para examinar esse tipo de coisa por si mesmo.Edit: Minha primeira versão descompilou a expressão geradora criada no escopo do módulo no prompt interativo. Isso é um pouco diferente da versão do OP com ele usado dentro de uma função. Eu modifiquei isso para corresponder ao caso real na questão.
Como você pode ver abaixo, o gerador de "rendimento" (primeiro caso) tem três instruções extras na configuração, mas desde o primeiro
FOR_ITER
elas diferem em apenas um aspecto: a abordagem de "rendimento" usa umLOAD_FAST
no lugar de umLOAD_DEREF
dentro do loop. OLOAD_DEREF
é "bastante mais lento" do queLOAD_FAST
, portanto, torna a versão de "rendimento" um pouco mais rápida do que a expressão geradora para valores grandes o suficiente dex
(o loop externo) porque o valor dey
é carregado um pouco mais rápido a cada passagem. Para valores menoresx
, seria um pouco mais lento por causa da sobrecarga extra do código de configuração.Também pode valer a pena apontar que a expressão geradora normalmente seria usada embutida no código, em vez de envolvê-la com a função dessa forma. Isso removeria um pouco da sobrecarga de configuração e manteria a expressão do gerador um pouco mais rápida para valores de loop menores, mesmo que
LOAD_FAST
desse à versão "rendimento" uma vantagem de outra forma.Em nenhum dos casos, a diferença de desempenho seria suficiente para justificar a decisão entre um ou outro. A legibilidade conta muito mais, portanto, use o que parecer mais legível para a situação em questão.
fonte
LOAD_DEREF
ser "um pouco mais lento", portanto, se o desempenho realmente importasse, algum tempo realtimeit
seria bom. Uma análise teórica vai apenas até certo ponto.Neste exemplo, não realmente. Mas
yield
pode ser usado para construções mais complexas - por exemplo , pode aceitar valores do chamador também e modificar o fluxo como resultado. Leia PEP 342 para mais detalhes (é uma técnica interessante que vale a pena conhecer).De qualquer forma, o melhor conselho é usar o que for mais claro para suas necessidades .
PS Aqui está um exemplo simples de co-rotina de Dave Beazley :
fonte
Não há diferença para o tipo de loops simples que você pode inserir em uma expressão de gerador. No entanto, o rendimento pode ser usado para criar geradores que fazem um processamento muito mais complexo. Aqui está um exemplo simples para gerar a sequência de fibonacci:
fonte
No uso, observe uma distinção entre um objeto gerador e uma função geradora.
Um objeto gerador é usado apenas uma vez, em contraste com uma função de gerador, que pode ser reutilizada cada vez que você chamá-lo novamente, porque retorna um novo objeto gerador.
As expressões geradoras são, na prática, normalmente usadas "brutas", sem envolvê-las em uma função, e retornam um objeto gerador.
Por exemplo:
que produz:
Compare com um uso ligeiramente diferente:
que produz:
E compare com uma expressão geradora:
que também produz:
fonte
Usar
yield
é bom se a expressão for mais complicada do que apenas loops aninhados. Entre outras coisas, você pode retornar um primeiro valor especial ou um último valor especial. Considerar:fonte
Ao pensar em iteradores, o
itertools
módulo:Para desempenho, considere
itertools.product(*iterables[, repeat])
fonte
Sim, há uma diferença.
Para a expressão geradora
(x for var in expr)
,iter(expr)
é chamado quando a expressão é criada .Ao usar
def
eyield
criar um gerador, como em:iter(expr)
ainda não foi chamado. Ele será chamado apenas durante a iteraçãog
(e pode nem ser chamado).Tomando este iterador como exemplo:
Este código:
enquanto:
Como a maioria dos iteradores não faz muitas coisas
__iter__
, é fácil não perceber esse comportamento. Um exemplo do mundo real seria o do DjangoQuerySet
, que busca dados__iter__
edata = (f(x) for x in qs)
pode levar muito tempo, enquantodef g(): for x in qs: yield f(x)
seguido pordata=g()
retornaria imediatamente.Para obter mais informações e a definição formal, consulte PEP 289 - Expressões do gerador .
fonte
Há uma diferença que pode ser importante em alguns contextos que ainda não foi apontada. Usar
yield
impede que você usereturn
para outra coisa que não gerar StopIteration implicitamente (e coisas relacionadas a corrotinas) .Isso significa que este código está mal formado (e alimentá-lo a um intérprete fornecerá um
AttributeError
):Por outro lado, este código funciona perfeitamente:
fonte