import inspect
class Deferred(object):
def __init__(self, expr):
self.expr = expr
def revaluatable(func):
varnames,_,_,defaults = inspect.getargspec(func)
num_varnames = len(varnames); num_defaults = len(defaults)
def wrapper(*args, **kwds):
if len(args) >= num_varnames: # defaults not used here
return func(*args,**kwds)
f_locals = dict(zip(varnames,args))
# maximum number of used defaults
max_defaults = min(num_defaults, num_varnames-len(args))
for var,default in zip(varnames[-max_defaults:],defaults[-max_defaults:]):
if var in kwds:
continue # passed as keyword argument; don't use the default
if not isinstance(default, Deferred):
f_locals[var] = default # non re-evaluatable default
else: # reevaluate default expr. in f_locals
f_locals[var] = eval(default.expr, func.func_globals, f_locals)
f_locals.update(kwds) # add any extra keyword arguments
return func(**f_locals)
return wrapper
#======= example ===============================================================
>>> G = 1 # some global
>>>
>>> @revaluatable
... def f(w, x=Deferred('x**2+G'), y=Deferred('w+x'), z=Deferred('[]'))
... z.extend([w,x,y]); return z
...
>>> f(3)
[3, 10, 13]
>>> G=3; f(4)
[4, 12, 16]
>>> f(4,5)
[4, 5, 9]
>>> f(-1,1,0)
[-1, 1, 0]
>>> from collections import deque
>>> f(-1, z=deque())
deque([-1, 12, 11])