Eu tenho o seguinte pedaço de código que falha com o seguinte erro:
RuntimeError: profundidade máxima de recursão excedida
Tentei reescrever isso para permitir a otimização da recursão de cauda (TCO). Acredito que esse código teria sido bem-sucedido se um TCO tivesse ocorrido.
def trisum(n, csum):
if n == 0:
return csum
else:
return trisum(n - 1, csum + n)
print(trisum(1000, 0))
Devo concluir que o Python não faz nenhum tipo de TCO ou preciso apenas defini-lo de maneira diferente?
python
recursion
stack
stack-overflow
tail-recursion
Jordan Mack
fonte
fonte
return func(...)
(explícita ou implicitamente), seja recursiva ou não. O TCO é um superconjunto adequado do TRE, e mais útil (por exemplo, viabiliza o estilo de passagem de continuação, o que o TRE não pode) e não é muito mais difícil de implementar.foo
de dentro de uma ligação parafoo
de dentro parafoo
dentro de uma ligação parafoo
... Acho que nenhuma informação útil seria perdida se você perdesse isso.Respostas:
Não, e nunca será, uma vez que Guido van Rossum prefere ter um rastreio adequado:
Eliminação da recursão da cauda (22-04-2009)
Palavras finais sobre chamadas de cauda (27-04-2009)
Você pode eliminar manualmente a recursão com uma transformação como esta:
fonte
from operator import add; reduce(add, xrange(n + 1), csum)
:?Publiquei um módulo executando otimização de chamada de cauda (manipulando o estilo de recursão de cauda e de passagem de continuação): https://github.com/baruchel/tco
Otimizando a recursão da cauda em Python
Afirma-se frequentemente que a recursão da cauda não se adequa à maneira Python de codificar e que não se deve preocupar em incorporá-la em um loop. Não quero discutir com esse ponto de vista; às vezes, no entanto, gosto de tentar ou implementar novas idéias como funções recursivas de cauda em vez de com loops por várias razões (focando na ideia e não no processo, tendo vinte funções curtas na tela ao mesmo tempo, em vez de apenas três "Pythonic" funções, trabalhando em uma sessão interativa em vez de editar meu código etc.).
Otimizar a recursão da cauda no Python é de fato bastante fácil. Embora se diga que é impossível ou muito complicado, acho que pode ser alcançado com soluções elegantes, curtas e gerais; Até acho que a maioria dessas soluções não usa os recursos do Python, do contrário. Expressões lambda limpas, trabalhando com loops muito padrão, levam a ferramentas rápidas, eficientes e totalmente utilizáveis para implementar a otimização da recursão da cauda.
Como conveniência pessoal, escrevi um pequeno módulo implementando essa otimização de duas maneiras diferentes. Eu gostaria de discutir aqui sobre minhas duas funções principais.
A maneira limpa: modificando o combinador Y
O combinador Y é bem conhecido; permite usar funções lambda de maneira recursiva, mas não permite por si só incorporar chamadas recursivas em um loop. O cálculo lambda sozinho não pode fazer uma coisa dessas. Uma ligeira alteração no combinador Y, no entanto, pode proteger a chamada recursiva a ser realmente avaliada. A avaliação pode, portanto, ser adiada.
Aqui está a famosa expressão do combinador Y:
Com uma pequena mudança, eu pude obter:
Em vez de chamar a si mesma, a função f agora retorna uma função que executa a mesma chamada, mas, como ela retorna, a avaliação pode ser feita posteriormente de fora.
Meu código é:
A função pode ser usada da seguinte maneira; Aqui estão dois exemplos com versões recursivas de fatorial e Fibonacci:
Obviamente, a profundidade da recursão não é mais um problema:
É claro que esse é o único objetivo real da função.
Somente uma coisa não pode ser feita com essa otimização: ela não pode ser usada com uma função recursiva de cauda avaliando para outra função (isso vem do fato de que objetos retornáveis que podem ser chamados são todos manipulados como chamadas recursivas adicionais sem distinção). Como geralmente não preciso desse recurso, estou muito feliz com o código acima. No entanto, para fornecer um módulo mais geral, pensei um pouco mais para encontrar alguma solução alternativa para esse problema (consulte a próxima seção).
No que diz respeito à velocidade desse processo (que não é o problema real), ele é bastante bom; funções recursivas de cauda são avaliadas até mais rapidamente do que com o código a seguir, usando expressões mais simples:
Penso que avaliar uma expressão, mesmo complicada, é muito mais rápida do que avaliar várias expressões simples, como é o caso nesta segunda versão. Eu não mantive essa nova função no meu módulo e não vejo circunstâncias em que ela possa ser usada, e não a "oficial".
Estilo de passagem de continuação com exceções
Aqui está uma função mais geral; é capaz de lidar com todas as funções recursivas da cauda, incluindo aquelas que retornam outras funções. Chamadas recursivas são reconhecidas de outros valores de retorno pelo uso de exceções. Esta solução é mais lenta que a anterior; um código mais rápido provavelmente poderia ser escrito usando alguns valores especiais como "sinalizadores" sendo detectados no loop principal, mas não gosto da idéia de usar valores especiais ou palavras-chave internas. Há alguma interpretação engraçada do uso de exceções: se o Python não gostar de chamadas recursivas de cauda, uma exceção deve ser levantada quando uma chamada recursiva de cauda ocorrer, e a maneira Pythonic será capturar a exceção para encontrar algumas informações limpas. solução, que é realmente o que acontece aqui ...
Agora todas as funções podem ser usadas. No exemplo a seguir,
f(n)
é avaliado para a função de identidade qualquer valor positivo de n:Certamente, pode-se argumentar que as exceções não se destinam a ser usadas para redirecionar intencionalmente o intérprete (como um tipo de
goto
declaração ou provavelmente como um tipo de estilo de passagem de continuação), o que devo admitir. Mas, novamente, acho engraçada a ideia de usartry
uma única linha como umareturn
declaração: tentamos retornar algo (comportamento normal), mas não podemos fazê-lo por causa de uma chamada recursiva (exceção).Resposta inicial (29/08/2013).
Eu escrevi um plugin muito pequeno para lidar com a recursão da cauda. Você pode encontrá-lo com minhas explicações aqui: https://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs
Ele pode incorporar uma função lambda escrita com um estilo de recursão de cauda em outra função que a avaliará como um loop.
O recurso mais interessante nessa pequena função, na minha humilde opinião, é que a função não depende de algum truque de programação sujo, mas de mero cálculo lambda: o comportamento da função é alterado para outro quando inserido em outra função lambda que parece muito com o combinador Y.
fonte
bet0
ser usada como um decorador para métodos de classe?def
sintaxe para suas funções e, na verdade, o último exemplo acima depende de uma condição. No meu post baruchel.github.io/python/2015/11/07/…, você pode ver um parágrafo começando com "Claro que você poderia objetar que ninguém escreveria esse código", onde eu dou um exemplo com a sintaxe de definição usual. Para a segunda parte da sua pergunta, tenho que pensar um pouco mais, pois não passo tempo nisso há um tempo. Saudações.A palavra Guido está em http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html
fonte
O CPython não suporta e provavelmente nunca suportará a otimização da chamada de cauda com base nas declarações de Guido van Rossum sobre o assunto.
Ouvi argumentos de que isso torna a depuração mais difícil por causa de como ela modifica o rastreamento da pilha.
fonte
Experimente a implementação de TCO de macropia experimental para obter o tamanho.
fonte
Além de otimizar a recursão da cauda, você pode definir a profundidade da recursão manualmente:
fonte