Recipe/Tools for easy and stable recursion of current function when only selected arguments have to be changed. No more mixup of argument positions/names. No more forgetting. Useful as well for realizing delayed/repeated/serialized function calls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | # simple dirty trick for automatic recursion: keyword args from locals()
def myfunc(a,b=1,c=2,d=3,e=4):
_kwargs=locals().copy() # freeze unconditional at very beginning
print locals()
if e<=0: return
else:
_kwargs['e']=e-1
myfunc(**_kwargs)
# proper tools for correct/ad-hoc/fast automatic recursion
def recursion_args(func, func_locals, **kwargs):
"""returns named positional args only - without original *args, **kwargs"""
co=func.func_code
return tuple([ kwargs.pop(co.co_varnames[i], func_locals[co.co_varnames[i]])
for i in range(co.co_argcount) ])
assert not kwargs
def recurse(func, func_locals, *args, **kwargs):
"""for ad-hoc recursion - in case: manual insertion of original *args/**kwargs.
Use it like recurse(myfunc,locals(),changed=changed-1,*args,**kwargs)"""
co=func.func_code
args = tuple([ kwargs.pop(co.co_varnames[i], func_locals[co.co_varnames[i]])
for i in range(co.co_argcount) ]) + args
return func(*args,**kwargs)
def recurse_ex(func, func_locals, **kwargs):
"""auto-handles original *args and **kwargs.
Use it like recurse_ex(myfunc,locals(),changed=changed-1)
"""
co=func.func_code
args = tuple([ kwargs.pop(co.co_varnames[i], func_locals[co.co_varnames[i]])
for i in range(co.co_argcount) ])
if co.co_flags & 0x04:
i+=1
args+=func_locals[co.co_varnames[i]]
if co.co_flags & 0x08:
kwargs.update(func_locals[co.co_varnames[i+1]]) #TODO:reverse priority?
return func(*args,**kwargs)
def recursor(func, func_locals):
co=func.func_code
def _recursor(**kwargs):
args=tuple([kwargs.pop(co.co_varnames[i],func_locals[co.co_varnames[i]])
for i in range(co.co_argcount) ])
return func(*args,**kwargs)
return _recursor
def recursor1(func):
# for recursion with some pre-computatation;
co=func.func_code
argnames=[ co.co_varnames[i] for i in range(co.co_argcount) ]
def _recursor1(func_locals,**kwargs):
return func( *tuple([ kwargs.pop(name,func_locals[name])
for name in argnames ]), **kwargs )
return _recursor1
# examples
def func1(a,b=1,c=2,d=3,e=4):
print locals()
if e<=0: return
else:
recurse(func1,locals(),
e=e-1 ) # recurse with e changed
def func2(a,b=1,c=2,d=3,e=4):
RECURSE=recursor(func2,locals()) # freeze it at the beginning
print locals()
if e<=0: return
else:
RECURSE(e=e-1)
def func3(a,b=1,c=2,d=3,e=4):
print locals()
if e<=0: return
else:
func3_recurse(locals(),
e=e-1 )
func3_recurse=recursor1(func3)
def func4(a,b=5,*args,**kwargs):
_v=1
print locals()
if b<=0: return
else:
recurse_ex(func4,locals(),
b=b-1 )
echo_args=None
def func_with_echo(a,b=1,c=2,d=3,e=4, echo=None):
print locals()
global echo_args
if not echo:
if echo_args:
func_with_echo(*echo_args)
echo_args=recursion_args(func_with_echo, locals(),
echo=1)
class Class:
def meth(self,a,b=1,c=2):
print locals()
if c<=0: return
else:
recurse(Class.meth,locals(),
c=c-1)
if __name__=='__main__':
myfunc('myfunc')
func1('func-1')
func2('func-2')
func3('func-3')
func4('func-4',5,4,3,2,1,extra='e')
Class().meth('E')
func_with_echo('first')
func_with_echo('second',c=7)
|
Generally useful. Often used in error recover/retry strategies.
Tested with Python 2.3, 2.4, 2.5. For Python2.2 the .pop()'s have to be replaced with .get() / worked around.
Maybe the recursor could be considered for the new 'functional' builtin module of Python 2.5 - and better reflexion options built-in for a future Python.
Speed impact on an elementary simple 2-liner recursion (Py2.3; print's removed): profile.run("for i in range(1000):func0('A')") : 81ms profile.run("for i in range(1000):func1('A')") : 285ms (recurse) profile.run("for i in range(1000):func2('A')") : 446ms (recursor) profile.run("for i in range(1000):func3('A')") : 239ms (recursor1) def func0(a,b=1,c=2,d=3,e=4): ____if e<=0: return ____else: func0(a,b,c,d,e-1)