Estou tendo dificuldade para envolver meu cérebro em torno do PEP 380 .
- Quais são as situações em que a "produção de" é útil?
- Qual é o caso de uso clássico?
- Por que é comparado com micro-threads?
[atualização]
Agora eu entendo a causa das minhas dificuldades. Eu usei geradores, mas nunca usei corotinas (introduzidas pelo PEP-342 ). Apesar de algumas semelhanças, geradores e corotinas são basicamente dois conceitos diferentes. Entender as corotinas (não apenas os geradores) é a chave para entender a nova sintaxe.
As rotinas IMHO são o recurso Python mais obscuro ; a maioria dos livros faz com que pareça inútil e desinteressante.
Obrigado pelas ótimas respostas, mas agradecimentos especiais ao agf e seu comentário, relacionados às apresentações de David Beazley . David balança.
Respostas:
Vamos tirar uma coisa do caminho primeiro. A explicação que
yield from g
é equivalentefor v in g: yield v
nem começa a fazer justiça ao queyield from
se trata. Porque, convenhamos, se tudo o queyield from
faz é expandir ofor
loop, ele não garante a adiçãoyield from
à linguagem e impede que um monte de novos recursos sejam implementados no Python 2.x.O que
yield from
faz é estabelecer uma conexão bidirecional transparente entre o chamador e o subgerador :A conexão é "transparente" no sentido de que também propagará tudo corretamente, não apenas os elementos que estão sendo gerados (por exemplo, as exceções são propagadas).
A conexão é "bidirecional" no sentido de que os dados podem ser enviados de e para um gerador.
( Se estivéssemos falando sobre o TCP,
yield from g
pode significar "agora desconecte temporariamente o soquete do meu cliente e reconecte-o a esse outro soquete do servidor". )BTW, se você não sabe ao certo o que significa enviar dados para um gerador , você precisa descartar tudo e ler primeiro sobre as corotinas - elas são muito úteis (contrastam com as sub - rotinas ), mas infelizmente são menos conhecidas no Python. O curioso curso de Dave Beazley sobre corotinas é um excelente começo. Leia os slides 24-33 para obter uma cartilha rápida.
Lendo dados de um gerador usando rendimento de
Em vez de iterar manualmente
reader()
, podemos justamenteyield from
.Isso funciona e eliminamos uma linha de código. E provavelmente a intenção é um pouco mais clara (ou não). Mas nada mudou a vida.
Enviando dados para um gerador (corotina) usando o rendimento de - Parte 1
Agora vamos fazer algo mais interessante. Vamos criar uma corotina chamada
writer
que aceita dados enviados a ele e grava em um soquete, fd, etc.Agora, a pergunta é: como a função do wrapper deve lidar com o envio de dados para o gravador, para que quaisquer dados enviados ao wrapper sejam enviados de forma transparente ao
writer()
?O wrapper precisa aceitar os dados que são enviados para ele (obviamente) e também deve manipular o
StopIteration
quando o loop for estiver esgotado. Evidentemente, apenas fazerfor x in coro: yield x
não serve. Aqui está uma versão que funciona.Ou, nós poderíamos fazer isso.
Isso economiza 6 linhas de código, torna muito mais legível e simplesmente funciona. Magia!
Enviando dados para um rendimento de gerador de - Parte 2 - Tratamento de exceções
Vamos torná-lo mais complicado. E se nosso escritor precisar lidar com exceções? Digamos que as
writer
alçasSpamException
ae***
sejam impressas se encontrarem uma.E se não mudarmos
writer_wrapper
? Funciona? Vamos tentarHum, não está funcionando porque
x = (yield)
apenas gera a exceção e tudo chega a um impasse. Vamos fazê-lo funcionar, mas manipulando exceções manualmente e enviando-as ou lançando-as no subgerador (writer
)Isso funciona.
Mas o mesmo acontece!
O
yield from
manipula de forma transparente o envio ou lançamento de valores no subgerador.Isso ainda não cobre todos os casos de canto. O que acontece se o gerador externo estiver fechado? E o caso em que o subgerador retorna um valor (sim, no Python 3.3+, os geradores podem retornar valores), como o valor retornado deve ser propagado? Que
yield from
lida com transparência em todos os casos de canto é realmente impressionante .yield from
funciona magicamente e lida com todos esses casos.Pessoalmente, considero
yield from
uma má escolha de palavra-chave porque não torna aparente a natureza bidirecional . Foram propostas outras palavras-chave (comodelegate
foram rejeitadas, porque adicionar uma nova palavra-chave ao idioma é muito mais difícil do que combinar as existentes.Em resumo, é melhor pensar
yield from
como umtransparent two way channel
entre o chamador eo sub-gerador.Referências:
fonte
except StopIteration: pass
INSIDE owhile True:
loop não é uma representação precisa deyield from coro
- que não é um loop infinito e depois decoro
esgotado (ou seja, gera StopIteration),writer_wrapper
executará a próxima instrução. Após a última declaração que vai-se auto-raiseStopIteration
como qualquer gerador exausta ...writer
contidofor _ in range(4)
em vez dewhile True
, em seguida, após a impressão>> 3
que também auto-raiseStopIteration
e este seria por cabo de autoyield from
e, em seguida,writer_wrapper
seria auto-raise é própriaStopIteration
e porquewrap.send(i)
não está dentrotry
do bloco, seria realmente levantou neste ponto ( ie traceback só irá relatar a linha comwrap.send(i)
, e não algo de dentro do gerador)Toda situação em que você tem um loop como este:
Como o PEP descreve, esta é uma tentativa bastante ingênua de usar o subgerador, faltam vários aspectos, especialmente o manuseio adequado dos mecanismos
.throw()
/.send()
/.close()
introduzidos pelo PEP 342 . Para fazer isso corretamente, é necessário um código bastante complicado .Considere que você deseja extrair informações de uma estrutura de dados recursiva. Digamos que queremos obter todos os nós das folhas em uma árvore:
Ainda mais importante é o fato de que até o momento
yield from
não havia um método simples de refatorar o código do gerador. Suponha que você tenha um gerador (sem sentido) como este:Agora você decide fatorar esses loops em geradores separados. Sem
yield from
, isso é feio, até o ponto em que você pensará duas vezes se realmente deseja fazê-lo. Comyield from
, é realmente bom olhar para:Acho que o que esta seção do PEP está falando é que todo gerador tem seu próprio contexto de execução isolado. Juntamente com o fato de a execução ser alternada entre o gerador-iterador e o chamador usando
yield
e__next__()
, respectivamente, isso é semelhante aos encadeamentos, em que o sistema operacional alterna o encadeamento em execução de tempos em tempos, juntamente com o contexto de execução (pilha, registradores, ...)O efeito disso também é comparável: o gerador-iterador e o chamador progridem em seu estado de execução ao mesmo tempo, suas execuções são intercaladas. Por exemplo, se o gerador fizer algum tipo de cálculo e o chamador imprimir os resultados, você os verá assim que estiverem disponíveis. Esta é uma forma de simultaneidade.
yield from
Porém, essa analogia não é nada específico - é uma propriedade geral dos geradores em Python.fonte
get_list_values_as_xxx
sejam geradores simples com uma única linhafor x in input_param: yield int(x)
e os outros dois respectivamente comstr
efloat
Onde quer que você invocar um gerador de dentro de um gerador que você precisa de uma "bomba" de re
yield
os valores:for v in inner_generator: yield v
. Como o PEP aponta, há complexidades sutis a isso que a maioria das pessoas ignora. Controle de fluxo não local comothrow()
é um exemplo dado no PEP. A nova sintaxeyield from inner_generator
é usada onde quer que você tenha escrito ofor
loop explícito antes. Porém, não é apenas um açúcar sintático: ele lida com todos os casos de canto que são ignorados pelofor
loop. Ser "açucarado" encoraja as pessoas a usá-lo e, assim, obter os comportamentos certos.Esta mensagem no tópico de discussão fala sobre essas complexidades:
Não posso falar de uma comparação com micro-threads, exceto observar que os geradores são um tipo de paralelismo. Você pode considerar o gerador suspenso como um encadeamento que envia valores
yield
para um encadeamento consumidor. A implementação real pode ser nada parecida com isso (e a implementação real é obviamente de grande interesse para os desenvolvedores do Python), mas isso não diz respeito aos usuários.A nova
yield from
sintaxe não adiciona nenhum recurso adicional ao idioma em termos de encadeamento, apenas facilita o uso correto dos recursos existentes. Ou, mais precisamente, torna mais fácil para um consumidor iniciante de um gerador interno complexo escrito por um especialista passar por esse gerador sem interromper nenhum de seus complexos recursos.fonte
Um pequeno exemplo o ajudará a entender um dos
yield from
casos de uso: obter valor de outro geradorfonte
print(*flatten([1, [2], [3, [4]]]))
yield from
basicamente interage iteradores de maneira eficiente:Como você pode ver, ele remove um loop Python puro. Isso é praticamente tudo o que faz, mas encadear iteradores é um padrão bastante comum no Python.
Threads são basicamente um recurso que permite que você pule de funções em pontos completamente aleatórios e volte ao estado de outra função. O supervisor de encadeamento faz isso com muita frequência, portanto, o programa parece executar todas essas funções ao mesmo tempo. O problema é que os pontos são aleatórios; portanto, é necessário usar o bloqueio para impedir que o supervisor interrompa a função em um ponto problemático.
Os geradores são bem parecidos com os threads nesse sentido: eles permitem que você especifique pontos específicos (sempre que eles
yield
) onde você pode entrar e sair. Quando usados dessa maneira, os geradores são chamados de corotinas.Leia estes excelentes tutoriais sobre corotinas em Python para obter mais detalhes
fonte
throw()/send()/close()
sãoyield
recursos queyield from
obviamente precisam ser implementados corretamente, pois é para simplificar o código. Tais trivialidades não têm nada a ver com o uso.chain
função, porqueitertools.chain
já existe. Useyield from itertools.chain(*iters)
.No uso aplicado para a corotina de E / S assíncrona ,
yield from
tem um comportamento semelhanteawait
ao de uma função de corotina . Ambos são usados para suspender a execução da corotina.yield from
é usado pela corotina baseada em gerador .await
é usado paraasync def
corotina. (desde Python 3.5 ou superior)Para o Asyncio, se não houver necessidade de oferecer suporte a uma versão mais antiga do Python (ou seja,> 3.5),
async def
/await
é a sintaxe recomendada para definir uma corotina. Assim,yield from
não é mais necessário em uma rotina.Mas no exterior geral da asyncio,
yield from <sub-generator>
ainda tem algum outro uso na iteração do sub-gerador como mencionado na resposta anterior.fonte
Esse código define uma função
fixed_sum_digits
retornando um gerador enumerando todos os seis dígitos, de forma que a soma dos dígitos seja 20.Tente escrever sem
yield from
. Se você encontrar uma maneira eficaz de fazer isso, me avise.Eu acho que para casos como este: visitar árvores,
yield from
torna o código mais simples e mais limpo.fonte
Simplificando,
yield from
fornece recursão final para as funções do iterador.fonte