This has to be the hackiest code I've ever written. I think it comes close to what you were asking for (another alternative I came up with was using a lambda inside the function definition, but it takes a bit too much room to be pretty IMO):
import inspect
from functools import wraps
class defaultArguments(object):
def __init__(self):
self.lazyArgs = []
def initialize(self, func):
lazyArgs, self.lazyArgs = self.lazyArgs, []
@wraps(func)
def functionWrapper(*args, **kw):
if lazyArgs:
argNames, defaults = inspect.getargspec(func)[::3]
argValues = list(args) + [
kw[y] if y in kw else defaults[x]
for x,y in enumerate(argNames[len(args):])
]
oldGlobals = {}
for n,v in zip(argNames, argValues):
try:
oldGlobals[n] = globals()[n]
except:
oldGlobals[n] = None
if v not in lazyArgs:
globals()[n] = v
else:
globals()[n] = kw[n] = eval(v)
for o,v in oldGlobals.items(): globals()[o] = v
return func(*args, **kw)
return functionWrapper
def __call__(self, x):
self.lazyArgs.append(x)
return x
Using it:
d = defaultArguments()
@d.initialize
def function1(a, start=d('a[-1]'), end=d('len(a)-1')):
print a, start, end
function1([1,2,8])
>>> [1, 2, 8] 8 2
function1([1,2,8,10], end=1)
>>> [1, 2, 8, 10] 10 1
@d.initialize
def function2(a, b, c, start=d('a*b*c'), end=d('a+b+c+start')):
print a, start, end
function2(2,4,6)
>>> 2 48 60
# Notice that `end` does take the calculated value of `start` into
# account. The logic here is based on what you'd expect to happen
# with normal assignment if the names were each assigned a value
# sequentially: a is evaluated, then b, then c, etc...
I do feel guilty for doing this, especially with the way I resorted to using globals and other cheats. However, I think it works as you requested.
Unfortunately, you do have to write extra stuff (using a decorator and having to wrap keyword values in d('') ), but that was inevitable as Python doesn't support this natively.
Edit:
I worked on the sugary part of the syntax a bit. Shortened it down to a simple decorator function.
def initArgs(func):
@wraps(func)
def functionWrapper(*args, **kw):
argNames, defaults = inspect.getargspec(func)[::3]
for k in kw:
for i in argNames:
if k != i and ('_' + k) == i:
kw['_' + k] = kw[k]
del kw[k]
argValues = list(args) + [
kw[y] if y in kw else defaults[x]
for x,y in enumerate(argNames[len(args):])
]
oldGlobals = {}
for n,v in zip(argNames, argValues):
try:
oldGlobals[n] = globals()[n]
except:
oldGlobals[n] = None
if not n.startswith('_') or n in kw:
globals()[n] = v
else:
globals()[n] = kw[n] = eval(v)
for o,v in oldGlobals.items(): globals()[o] = v
return func(*args, **kw)
return functionWrapper
To use it:
# When using initArgs, the strings associated with the keyword arguments will
# get eval'd only if the name is preceded with an underscore(`_`). It's a
# bit strange and unpythonic for part of a name to have side effects, but then
# again name mangling works with double underscores (`__`) before methods.
# Example:
@initArgs
def function1(a, _start='a[-1]', _end='len(a)-1'):
print a, _start, _end
function1([1,2,8,10])
>>> [1, 2, 8, 10] 10 3
# Removing underscore (`_`) from start
@initArgs
def function2(a, start='a[-1]', _end='len(a)-1'):
print a, start, _end
function1([1,2,8,10])
>>> [1, 2, 8, 10] 'a[-1]' 3
# Outputs a string normally.
In the caller's frame, the arguments start and end can used with or without their underscores, so changing their names in the function definition at a later point wouldn't affect the caller. The only exception is within the function itself, where removing an underscore(_) would require doing the same everywhere else inside.
def function(a, start=0, end=None):solution seems the most obvious to me as in Python you do not useints with "weird" value for a different meaning - because Python like explicit over implicit.Nonehas the exact meaning you're looking for i.e. no value was specified. It also allows you for a more explicit and shorter assignmentend = end or len(a)end = end or len(a)will assignlen(a)toendifendis0. One would need to doend = end if end is not None else len(a).-1instead ofNoneas in listsa[-1] == a[len(a) - 1], but I agree thatNonelooks more appropiate