Tenho o seguinte código:
def my_zip(*iterables):
iterators = tuple(map(iter, iterables))
while True:
yield tuple(map(next, iterators))
Quando my_zip
é chamado, ele apenas cria um loop infinito e nunca termina. Se eu inserir uma instrução print (como mostrado abaixo), é revelado que my_zip
está produzindo tuplas vazias infinitamente!
def my_zip(*iterables):
iterators = tuple(map(iter, iterables))
while True:
t = tuple(map(next, iterators))
print(t)
yield t
Entretanto, o código equivalente com uma expressão geradora funciona bem:
def my_genexp_zip(*iterables):
iterators = tuple(iter(it) for it in iterables)
while True:
try:
yield tuple(next(it) for it in iterators)
except:
print("exception caught!")
return
Por que a função map
não está se comportando como esperado? (Ou, se for o comportamento esperado, como eu poderia modificar seu comportamento para corresponder ao da função usando a expressão do gerador?)
Estou testando com o seguinte código:
print(list(my_genexp_zip(range(5), range(0, 10, 2))))
print(list(my_zip(range(5), range(0, 10, 2))))
Os dois trechos de código que você forneceu não são realmente "equivalentes", com a função usando expressões geradoras tendo notavelmente um manipulador de exceções "pega-tudo" em torno da expressão geradora produzindo itens para saída de tupla.
E se você realmente tornar as duas funções "equivalentes" removendo o manipulador de exceções:
você obterá um rastreamento de:
Então, está claro agora que a razão pela qual seu loop infinito com
while True:
pode terminar com sua versão de expressão do gerador da função é porque aRuntimeError
é capturado pelo seu manipulador de exceção catch-all, que retorna da função.E isso ocorre porque, desde o Python 3.7 , com a implementação do PEP-479 ,
StopIteration
o gerado dentro de um gerador é automaticamente transformado em umRuntimeError
para não ser confundido com oStopIteration
gerado por um gerador esgotado.Se você tentar seu código em uma versão anterior do Python (como 2.7), você verá que a versão da expressão do gerador da função fica presa no loop infinito também, onde a
StopIteration
exceção levantada pornext
bubbles sai do gerador e é manipulada pelotuple
construtor para produzir uma tupla vazia, assim como amap
versão da sua função. E abordar esse efeito de mascaramento de exceção é exatamente o motivo pelo qual o PEP-479 foi proposto e implementado.Sua
map
versão tem um loop infinito explícito. A única maneira de parar é se o corpo levantou uma exceção. Mas omap
iterador deixa oStopIteration
from danext
chamada com falha cair, e entãotuple
simplesmente pensamap
que levantou e para (resultando em tuplas vazias assim que o primeiro iterador se esgota).Isso é esperado, a
roundrobin
receita do itertools por exemplo também usa esse truque:Aqui, cada
next
chamada com falha simplesmente faz comyield from
que o seja interrompido e, então, o algoritmo continua com os iteradores restantes (enquanto algum ainda estiver ativo).Você pode consertar sua
map
versão, por exemplo, verificando o comprimento da tupla ebreak
ing se for menor queiterables
. Ou você pode encadear umRuntimeError
iterador -raising em todos os iteráveis e capturar isso como na sua versão genexp:Tente isso online!