Eu tenho um objeto gerador retornado por vários rendimentos. A preparação para chamar esse gerador é uma operação bastante demorada. É por isso que quero reutilizar o gerador várias vezes.
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
Claro, estou pensando em copiar o conteúdo para uma lista simples. Existe uma maneira de redefinir meu gerador?
y = list(y)
o restante do seu código inalterado.Geradores não podem ser rebobinados. Você tem as seguintes opções:
Execute a função do gerador novamente, reiniciando a geração:
Armazene os resultados do gerador em uma estrutura de dados na memória ou no disco que você pode iterar novamente:
A desvantagem da opção 1 é que ela calcula os valores novamente. Se isso exige muita CPU, você acaba calculando duas vezes. Por outro lado, a desvantagem de 2 é o armazenamento. A lista inteira de valores será armazenada na memória. Se houver muitos valores, isso pode ser impraticável.
Então você tem a troca clássica de memória versus processamento . Não consigo imaginar uma maneira de rebobinar o gerador sem armazenar os valores ou calculá-los novamente.
fonte
fonte
Provavelmente, a solução mais simples é envolver a peça cara em um objeto e passá-la ao gerador:
Dessa forma, você pode armazenar em cache os cálculos caros.
Se você puder manter todos os resultados na RAM ao mesmo tempo, use
list()
para materializar os resultados do gerador em uma lista simples e trabalhe com isso.fonte
Quero oferecer uma solução diferente para um problema antigo
O benefício disso, quando comparado a algo semelhante,
list(iterator)
é que isso éO(1)
complexidade do espaço elist(iterator)
éO(n)
. A desvantagem é que, se você tiver apenas acesso ao iterador, mas não a função que produziu o iterador, não poderá usar esse método. Por exemplo, pode parecer razoável fazer o seguinte, mas não funcionará.fonte
Se a resposta de GrzegorzOledzki não for suficiente, você provavelmente poderia usar
send()
para atingir seu objetivo. Consulte PEP-0342 para obter mais detalhes sobre geradores aprimorados e expressões de rendimento.UPDATE: Veja também
itertools.tee()
. Envolve parte dessa troca de memória versus processamento mencionada acima, mas pode economizar memória ao armazenar apenas os resultados do gerador em alist
; depende de como você está usando o gerador.fonte
Se o seu gerador é puro, no sentido de que sua saída depende apenas dos argumentos passados e do número da etapa, e você deseja que o gerador resultante seja reiniciado, aqui está um trecho de classificação que pode ser útil:
saídas:
fonte
Da documentação oficial do tee :
Portanto, é melhor usar
list(iterable)
no seu caso.fonte
list()
coloca toda a iteráveis na memóriatee()
se um iterador consumir todos os valores - é assim quetee
funciona.Usando uma função de wrapper para manipular
StopIteration
Você pode escrever uma função de wrapper simples na função de geração do gerador que rastreia quando o gerador está esgotado. Isso será feito usando a
StopIteration
exceção que um gerador lança quando atinge o final da iteração.Como você pode ver acima, quando nossa função wrapper captura uma
StopIteration
exceção, ela simplesmente reinicializa o objeto gerador (usando outra instância da chamada de função).E então, supondo que você defina sua função de fornecimento de gerador em algum lugar como abaixo, você pode usar a sintaxe do decorador da função Python para envolvê-la implicitamente:
fonte
Você pode definir uma função que retorne seu gerador
Agora você pode fazer quantas vezes quiser:
fonte
Não sei ao certo o que você quis dizer com preparação cara, mas acho que você realmente
Se for esse o caso, por que não reutilizar
data
?fonte
Não há opção para redefinir iteradores. O iterador geralmente aparece quando itera através da
next()
função. A única maneira é fazer um backup antes de iterar no objeto iterador. Confira abaixo.Criando objeto iterador com itens de 0 a 9
Iterando através da função next () que será exibida
Convertendo o objeto iterador na lista
então o item 0 já foi exibido. Todos os itens também são exibidos quando convertemos o iterador em lista.
Portanto, você precisa converter o iterador em listas para backup antes de começar a iterar. A lista pode ser convertida em iterador com
iter(<list-object>)
fonte
Agora você pode usar
more_itertools.seekable
(uma ferramenta de terceiros) que permite redefinir os iteradores.Instalar via
> pip install more_itertools
Nota: o consumo de memória aumenta ao avançar o iterador, portanto, tenha cuidado com iteráveis grandes.
fonte
Você pode fazer isso usando itertools.cycle (). Você pode criar um iterador com esse método e, em seguida, executar um loop for sobre o iterador, que fará um loop sobre seus valores.
Por exemplo:
irá gerar 20 números, de 0 a 4 repetidamente.
Uma observação dos documentos:
fonte
Ok, você diz que deseja ligar para um gerador várias vezes, mas a inicialização é cara ... E algo assim?
Como alternativa, você pode criar sua própria classe que segue o protocolo do iterador e define algum tipo de função 'reset'.
https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html
fonte
__call__
Minha resposta resolve um problema ligeiramente diferente: se o gerador é caro para inicializar e cada objeto gerado é caro para gerar. Mas precisamos consumir o gerador várias vezes em várias funções. Para chamar o gerador e cada objeto gerado exatamente uma vez, podemos usar threads e executar cada um dos métodos de consumo em threads diferentes. Podemos não alcançar um verdadeiro paralelismo devido ao GIL, mas alcançaremos nosso objetivo.
Essa abordagem fez um bom trabalho no seguinte caso: o modelo de aprendizado profundo processa muitas imagens. O resultado são muitas máscaras para muitos objetos na imagem. Cada máscara consome memória. Temos cerca de 10 métodos que produzem estatísticas e métricas diferentes, mas eles capturam todas as imagens de uma só vez. Todas as imagens não cabem na memória. Os métodos podem ser reescritos facilmente para aceitar o iterador.
Uso:
fonte
itertools.islice
ou para assíncronoaiostream.stream.take
, e este post permite fazê-lo de maneira assíncrona / aguardada stackoverflow.com/a/42379188/149818Isso pode ser feito por objeto de código. Aqui está o exemplo.
1 2 3 4
1 2 3 4
fonte
exec
isso ligeiramente não recomendado para casos tão simples.