Python has an extremely flexible object model that allows for assignment of arbitrary objects to arbitrary attributes of instances. I would like to do something like this: def button( self ): print "Pressed!" and then assign it to an instance: mywidget.bigred = button and call it: mywidget.bigred()
However this doesn't work because the attribute needs to be a bound method, not a function. Also, the name of the installed function remains button (eg. in stack traces), when we would like it to be bigred.
This recipe provides the installmethod() and renamefunction() functions to solve this problem.
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 | def installmethod(function, object, name = None):
"""
This function adds either a bound method to an instance or
an unbound method to a class. If name is ommited it defaults
to the name of the given function.
Example:
a = A()
def f(self, x, y):
self.z = x + y
installmethod(f, A, "add")
a.add(2, 4)
print a.z
installmethod(lambda self, i: self.l[i], a, "listIndex")
print a.listIndex(5)
"""
from types import ClassType, MethodType, InstanceType
if name == None:
name = function.func_name
else:
function = renamefunction(function, name)
if type(object) == ClassType:
setattr(object, name,
MethodType(function, None, object))
elif type(object) == InstanceType:
setattr(object, name,
MethodType(function, object, object.__class__))
else:
raise TypeError
def renamefunction(function, name):
"""
This function returns a function identical to the given one, but
with the given name.
"""
from types import FunctionType, CodeType
c = function.func_code
if c.co_name != name:
# rename the code object.
c = CodeType(c.co_argcount, c.co_nlocals, c.co_stacksize,
c.co_flags, c.co_code, c.co_consts,
c.co_names, c.co_varnames, c.co_filename,
name, c.co_firstlineno, c.co_lnotab)
if function.func_defaults != None:
return FunctionType(c, function.func_globals, name,
function.func_defaults)
return FunctionType(c, function.func_globals, name)
if __name__ == '__main__':
# example
class Widget:
def installButton(self, name):
def f(self, name=name):
print name, 'pressed'
installmethod(f, self, name)
a = Widget()
a.installButton('foo')
a.foo()
# AttributeError: Widget().foo()
def f(self, x, y):
self.z = x + y
Widget.add = renamefunction(f, "add")
a.add(2, 4)
print a.z
installmethod(lambda self, i: self.l[i], Widget, "listIndex")
a.l = list("abc")
print a.listIndex(1)
# TypeError: a.add(2, 4, 6)
|
See the examples above in the __main__ block. Note also that we can install lambda functions (which have no name) and the resulting method will have our given name "listIndex" both as the attribute name and in any subsequent stack traces.
I originally wanted to suggest these functions as an addition to the standard new.py module. However this has been deprecated in a very neat way by replacing all the new.py functions with types (as in "from types import ClassType as classobj"). I don't know where installmethod() and renamefunction() might belong in the python distribution now. Any ideas?
Use isinstance rather than comparing types. For the sake of robustness I'd suggest using, e.g., isinstance(object, ClassType) rather than type(object) == ClassType.