J'ai le morceau de code suivant qui échoue avec l'erreur suivante:
RuntimeError: profondeur de récursivité maximale dépassée
J'ai essayé de réécrire ceci pour permettre l'optimisation de la récursivité de queue (TCO). Je pense que ce code aurait dû réussir si un TCO avait eu lieu.
def trisum(n, csum):
if n == 0:
return csum
else:
return trisum(n - 1, csum + n)
print(trisum(1000, 0))
Dois-je conclure que Python ne fait aucun type de TCO, ou dois-je simplement le définir différemment?
python
recursion
stack
stack-overflow
tail-recursion
Jordan Mack
la source
la source
return func(...)
(explicitement ou implicitement), qu'elle soit récursive ou non. TCO est un sur-ensemble approprié de TRE, et plus utile (par exemple, il rend possible le style de passage de continuation, ce que TRE ne peut pas), et pas beaucoup plus difficile à mettre en œuvre.foo
de l'intérieur un appel versfoo
de l'intérieur versfoo
de l'intérieur un appel versfoo
... Je ne pense pas que des informations utiles seraient perdues en perdant cela.Réponses:
Non, et ce ne sera jamais le cas puisque Guido van Rossum préfère pouvoir avoir des retraits corrects:
Élimination de la récursivité de la queue (2009-04-22)
Derniers mots sur les appels de queue (2009-04-27)
Vous pouvez éliminer manuellement la récursivité avec une transformation comme celle-ci:
la source
from operator import add; reduce(add, xrange(n + 1), csum)
:?J'ai publié un module effectuant l'optimisation des appels de queue (gérant à la fois le style de récursion de queue et de passage de continuation): https://github.com/baruchel/tco
Optimisation de la récursivité de queue en Python
Il a souvent été affirmé que la récursivité de la queue ne convenait pas à la méthode de codage Pythonic et que l'on ne devrait pas se soucier de la façon de l'intégrer dans une boucle. Je ne veux pas contester ce point de vue; parfois cependant j'aime essayer ou implémenter de nouvelles idées en tant que fonctions récursives de queue plutôt qu'avec des boucles pour diverses raisons (se concentrer sur l'idée plutôt que sur le processus, avoir vingt fonctions courtes sur mon écran en même temps plutôt que seulement trois "Pythonic" fonctions, travailler dans une session interactive plutôt que de modifier mon code, etc.).
L'optimisation de la récursivité de queue en Python est en fait assez simple. Bien qu'il soit dit impossible ou très délicat, je pense qu'il peut être atteint avec des solutions élégantes, courtes et générales; Je pense même que la plupart de ces solutions n'utilisent pas les fonctionnalités Python autrement qu'elles ne le devraient. Des expressions lambda propres fonctionnant avec des boucles très standard conduisent à des outils rapides, efficaces et entièrement utilisables pour implémenter l'optimisation de récursivité de queue.
Pour plus de commodité, j'ai écrit un petit module implémentant une telle optimisation de deux manières différentes. Je voudrais discuter ici de mes deux fonctions principales.
La voie propre: modifier le combinateur Y
Le combinateur Y est bien connu; il permet d'utiliser les fonctions lambda de manière récursive, mais il ne permet pas à lui seul d'incorporer des appels récursifs dans une boucle. Le calcul lambda à lui seul ne peut pas faire une telle chose. Un léger changement dans le combinateur Y peut cependant protéger l'appel récursif à évaluer réellement. L'évaluation peut ainsi être retardée.
Voici la célèbre expression du combinateur Y:
Avec un très léger changement, j'ai pu obtenir:
Au lieu de s'appeler elle-même, la fonction f renvoie maintenant une fonction effectuant le même appel, mais puisqu'elle le renvoie, l'évaluation peut être effectuée plus tard de l'extérieur.
Mon code est:
La fonction peut être utilisée de la manière suivante; voici deux exemples avec les versions récursives de queue de factorielle et de Fibonacci:
De toute évidence, la profondeur de récursivité n'est plus un problème:
C'est bien sûr le seul véritable objectif de la fonction.
Une seule chose ne peut pas être faite avec cette optimisation: elle ne peut pas être utilisée avec une fonction récursive de queue évaluant une autre fonction (cela vient du fait que les objets retournés appelables sont tous traités comme des appels récursifs supplémentaires sans distinction). Comme je n'ai généralement pas besoin d'une telle fonctionnalité, je suis très satisfait du code ci-dessus. Cependant, afin de fournir un module plus général, j'ai réfléchi un peu plus afin de trouver une solution de contournement pour ce problème (voir la section suivante).
Concernant la rapidité de ce processus (qui n'est pas le vrai problème cependant), il se trouve être assez bon; Les fonctions récursives de queue sont même évaluées beaucoup plus rapidement qu'avec le code suivant à l'aide d'expressions plus simples:
Je pense que l'évaluation d'une expression, même compliquée, est beaucoup plus rapide que l'évaluation de plusieurs expressions simples, ce qui est le cas dans cette deuxième version. Je n'ai pas gardé cette nouvelle fonction dans mon module, et je ne vois aucune circonstance où elle pourrait être utilisée plutôt que celle "officielle".
Style de passage de continuation avec exceptions
Voici une fonction plus générale; il est capable de gérer toutes les fonctions récursives de queue, y compris celles renvoyant d'autres fonctions. Les appels récursifs sont reconnus à partir d'autres valeurs de retour par l'utilisation d'exceptions. Cette solution est plus lente que la précédente; un code plus rapide pourrait probablement être écrit en utilisant des valeurs spéciales comme "drapeaux" détectés dans la boucle principale, mais je n'aime pas l'idée d'utiliser des valeurs spéciales ou des mots clés internes. Il y a une interprétation amusante de l'utilisation des exceptions: si Python n'aime pas les appels récursifs à la queue, une exception devrait être levée lorsqu'un appel récursif à la queue se produit, et la manière Pythonique sera de capturer l'exception afin de trouver des éléments propres solution, qui est en fait ce qui se passe ici ...
Maintenant, toutes les fonctions peuvent être utilisées. Dans l'exemple suivant,
f(n)
est évalué à la fonction d'identité pour toute valeur positive de n:Bien sûr, on pourrait faire valoir que les exceptions ne sont pas destinées à être utilisées pour rediriger intentionnellement l'interprète (comme une sorte de
goto
déclaration ou probablement plutôt une sorte de style de passage de continuation), ce que je dois admettre. Mais, encore une fois, je trouve drôle l'idée d'utilisertry
une seule ligne comme unereturn
déclaration: nous essayons de renvoyer quelque chose (comportement normal) mais nous ne pouvons pas le faire à cause d'un appel récursif (exception).Réponse initiale (2013-08-29).
J'ai écrit un très petit plugin pour gérer la récursivité de la queue. Vous pouvez le trouver avec mes explications là-bas: https://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs
Il peut incorporer une fonction lambda écrite avec un style de récursivité de queue dans une autre fonction qui l'évaluera comme une boucle.
La caractéristique la plus intéressante de cette petite fonction, à mon humble avis, est que la fonction ne repose pas sur un hack de programmation sale mais sur un simple calcul lambda: le comportement de la fonction est changé en un autre lorsqu'elle est insérée dans une autre fonction lambda qui ressemble beaucoup au combinateur Y.
la source
bet0
peut-elle être utilisée comme décorateur pour les méthodes de classe?def
syntaxe pour vos fonctions, et en fait le dernier exemple ci-dessus repose sur une condition. Dans mon article baruchel.github.io/python/2015/11/07/… vous pouvez voir un paragraphe commençant par "Bien sûr, vous pourriez objecter que personne n'écrirait un tel code" où je donne un exemple avec la syntaxe de définition habituelle. Pour la deuxième partie de votre question, je dois y réfléchir un peu plus car je n'y ai pas consacré de temps depuis un moment. Cordialement.Le mot de Guido est sur http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html
la source
CPython ne prend pas et ne soutiendra probablement jamais l'optimisation des appels de queue basée sur les déclarations de Guido van Rossum sur le sujet.
J'ai entendu des arguments selon lesquels cela rend le débogage plus difficile en raison de la façon dont il modifie la trace de la pile.
la source
Essayez la mise en œuvre expérimentale du TCO de la macropie pour la taille.
la source
Outre l'optimisation de la récursivité de la queue, vous pouvez définir manuellement la profondeur de récursivité en:
la source