Here is what I want to do:
class demo(object):
def a(self):
pass
def b(self, param=self.a): #I tried demo.a as well after making a static
param()
The problem is apparently that one can't access the class in the function declaration line. Is there a way to add a prototype like in c(++)?
At the moment I use a ugly workarround:
def b(self, param=True): #my real function shall be able to use None, to skip the function call
if param == True:
param = self.a
if param != None: #This explainds why I can't take None as default,
#for param, I jsut needed something as default which was
#neither none or a callable function (don't want to force the user to create dummy lambdas)
param()
So is it possible to achieve something like described in the top part without this ugly workarround? Note bene: I am bound to Jython which is approximately python 2.5 (I know there is 2.7 but I can't upgrade)
I'll answer this question again, contradicting my earlier answer:
Short answer: YES! (sort of)
With the help of a method decorator, this is possible. The code is long and somewhat ugly, but the usage is short and simple.
The problem was that we can only use unbound methods as default arguments. Well, what if we create a wrapping function -- a decorator -- which binds the arguments before calling the real function?
First we create a helper class that can perform this task.
from inspect import getcallargs
from types import MethodType
from functools import wraps
class MethodBinder(object):
def __init__(self, function):
self.function = function
def set_defaults(self, args, kwargs):
kwargs = getcallargs(self.function, *args, **kwargs)
# This is the self of the method we wish to call
method_self = kwargs["self"]
# First we build a list of the functions that are bound to self
targets = set()
for attr_name in dir(method_self):
attr = getattr(method_self, attr_name)
# For older python versions, replace __func__ with im_func
if hasattr(attr, "__func__"):
targets.add(attr.__func__)
# Now we check whether any of the arguments are identical to the
# functions we found above. If so, we bind them to self.
ret = {}
for kw, val in kwargs.items():
if val in targets:
ret[kw] = MethodType(val, method_self)
else:
ret[kw] = val
return ret
So instances of MethodBinder
are associated with a method (or rather a function that will become a method). MethodBinder
s method set_defaults
may be given the arguments used to call the associated method, and it will bind any unbound method of the self
of the associated method and return a kwargs dict that may be used to call the associated method.
Now we can create a decorator using this class:
def bind_args(f):
# f will be b in the below example
binder = MethodBinder(f)
@wraps(f)
def wrapper(*args, **kwargs):
# The wrapper function will get called instead of b, so args and kwargs
# contains b's arguments. Let's bind any unbound function arguments:
kwargs = binder.set_defaults(args, kwargs)
# All arguments have been turned into keyword arguments. Now we
# may call the real method with the modified arguments and return
# the result.
return f(**kwargs)
return wrapper
Now that we've put the uglyness behind us, let's show the simple and pretty usage:
class demo(object):
def a(self):
print("{0}.a called!".format(self))
@bind_args
def b(self, param=a):
param()
def other():
print("other called")
demo().b()
demo().b(other)
This recipe uses a rather new addition to python, getcallargs
from inspect
. It's available only in newer versions of python2.7 and 3.1.