K
Kay said:
Diez said:Neat.
Diez
I'm not convinced by this. You have to recognise that the function is usingKay said:Hi Diez,
for all those who already copied and pasted the original solution and
played with it I apologize for radical changes in the latest version (
the recipe is on version 1.5 now ! ). The latest implementation is
again a lot faster than the previous one. It does not only get rid of
exceptions but also of stack-frame inspection.
Regards,
Kay
Kay said:for all those who already copied and pasted the original solution and
played with it I apologize for radical changes in the latest version (
the recipe is on version 1.5 now ! ). The latest implementation is
again a lot faster than the previous one. It does not only get rid of
exceptions but also of stack-frame inspection.
Michele said:Using my decorator module 'tail_recursive' can even be turned in a
signature-preserving
decorator. I think I will add this great example to the documentation
of the next version
of decorator.py!
Michele Simionato
Michele said:CONTINUE = object() # sentinel value returned by iterfunc
def tail_recursive(func):
"""
tail_recursive decorator based on Kay Schluehr's recipe
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691
"""
var = dict(in_loop=False, cont=True, argkw='will be set later')
# the dictionary is needed since Python closures are read-only
def iterfunc(*args, **kwd):
var["cont"] = not var["cont"]
if not var["in_loop"]: # start looping
var["in_loop"] = True
while True:
res = func(*args,**kwd)
if res is CONTINUE:
args, kwd = var["argkw"]
else:
var["in_loop"] = False
return res
else:
if var["cont"]:
var["argkw"] = args, kwd
return CONTINUE
else:
return func(*args,**kwd)
return iterfunc
[...]I'm not convinced by this. You have to recognise that the function is using
tail recursion, and then you have to modify the code to know that it is
using tail recursion. This is not always trivial. For example, the given
example is:
@tail_recursion
def factorial(n, acc=1):
"calculate a factorial"
if n == 0:
return acc
res = factorial(n-1, n*acc)
return res
but a more common way to write the function would be:
@tail_recursion
def factorial(n):
"calculate a factorial"
if n == 0:
return 1
return n * factorial(n-1)
which won't work because it isn't actually tail recursion, but it looks
sufficiently close to tail recursion that it would probably mislead a lot
of people into expecting it will work. If you are going to have to rewrite
functions in a stilted manner, and they use simple tail recursion, then why
not just factor out the tail recursion in the first place.
Tim said:I don't know why it wouldn't work this way, or why it isn't
tail-recursion?
From the google page do "define: tail recursion"
I tried the tail_recursion decorator from the cookbook-recipe with both
definitions of factorial, and I tried both definitions of the factorial
function with and without tail_recursion decorator.
In all four cases I get the same results, so it does work with both
definitions of factorial(), even if (according to you) the second
definition is not proper tail-recursion.
Using the tail-recursion decorator (the version that does not inspect
the stackframes) I get a small performance-increase over using the
factorial-function undecorated.
However, calculating factorial(1000) with the factorial-function as
defined in the cookbook-recipe is much much faster than calculating the
same factorial(1000) with the factorial-function you gave!
I cannot yet explain why the first function has so much better
performance than the second function - about a factor 10 difference,
in both python2.4.3 and python 2.5a2
Tim said:The other thing I do not understand, due to my limited understanding of
what is tail-recursion: factorial2 (Duncan's definition) is not proper
tail-recursion. Why not? How does it differ from 'real' tail recursion?
[...]Duncan said:Tim N. van der Leeuw wrote:
[...]
@tail_recursion
def factorial2(n):
# do the stuff
pass
your 'do the stuff' actually had an erroneous call to 'factorial'. If you
are going to rename the function you have to rename the recursive calls as
well. (At least, that's what I forgot to do when I first tried it and
couldn't understand why it gave me an answer instead of crashing.)
Duncan said:The decorator also fails for functions which are tail-recursive but which
contain other non-tail recursive calls within themselves. For example I
would be pretty sure you couldn't write a working implementation of
Ackermann's function using the decorator:
def Ack(M, N):
if (not M):
return( N + 1 )
if (not N):
return( Ack(M-1, 1) )
return( Ack(M-1, Ack(M, N-1)) )
Duncan Booth said:Tail recursion is when a function calls itself and then immediately returns
the result of that call as its own result.
I think the definition is broader than that so that these two functions
would
also be tail-recursive (i.e. the tail call doesn't have to be a self-tail
call; I might be mistaken, don't have a good reference at hand; however
"properly tail recursive" certainly refers to being able to do the below
without exhausting the stack even for large n, not just transforming
self-tail
calls to a loop, which is sort of limited usefulness anyway):
def even(n):
return n == 0 or not odd(n-1)
def odd(n):
return n == 1 or not even(n-1)
Very clever, although simulating a stack isn't exactly eliminatingKay said:Duncan said:The decorator also fails for functions which are tail-recursive but
which contain other non-tail recursive calls within themselves. For
example I would be pretty sure you couldn't write a working
implementation of Ackermann's function using the decorator:
def Ack(M, N):
if (not M):
return( N + 1 )
if (not N):
return( Ack(M-1, 1) )
return( Ack(M-1, Ack(M, N-1)) )
Definitely. The translation into a proper tail-recursive form is
non-trivial but nevertheless possible as demonstrated by the following
Ackermann implementation:
@tail_recursion
def ack(m,n,s=[0]): # use a stack-variable s as "accumulator"
if m==0:
if s[0] == 1:
return ack(s[1]-1,n+1,s[2])
elif s[0] == 0:
return n+1
elif n==0:
return ack(m-1,1,s)
else:
return ack(m,n-1,[1,m,s])
Duncan said:My other problem with this is that the decorator is very fragile although
this may be fixable
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.