Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 72 lines
 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?

1 comment

Hamish Lawson 18 years, 2 months ago  # | flag

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.

Created by john mclaughlin on Fri, 19 Sep 2003 (PSF)
Python recipes (4591)
john mclaughlin's recipes (1)

Required Modules

Other Information and Tasks