Python Tricks: AOP Variable-Set Pointcuts

I've been working on a project, a game fight simulator, where I want to watch the value of a variable, say the players health, in a GUI. The tricky part is I really don't want to couple the game logic to the GUI. I want to be able to simply have self.health -= damage be reflected in the GUI with out putting GUI code in the same function. Enter aspect oriented programming (AOP). Now python doesn't have a serious AOP module like Java and its AspectJ or Ruby and AspectR. So no AspectP or PyAspect will save us here. Instead, we have have to implement our on method of inserting a pointcut around setting the self.health variable.

Here's a simple class to get us started:

class Foo(object):
	def __init__(self, v):
		self.v = v
		
	def __str__(self):
		return 'Foo(v=%s)' % self.v

Now we test are little class:

f = Foo(5)
print f
f.v = 10
print f

OUTPUT====>
Foo(v=5)
Foo(v=10)

Yay! For simple examples, but you get the idea. We set a variable v in the object f. Now we introduce the pointcut code which lets us watch for variable sets:

def wrapProperties(klass):
	# keep the orginal _setattr__ incase its a custom method
	# for the class
	klass.__org__setattr__ = klass.__setattr__
	
	# define our new __setattr__ function
	def new__setattr__(self, name, value):
		# check for the watcher function
		funcname = '__set__%s' % name
		if hasattr(self, funcname):
			# if the function exists and the attribute we are watching
			# exits (ie its not the initial set) get the current value
			if hasattr(self, name):
				old_value = self.__getattribute__(name)
			else:
				old_value = None
			
			# grab the watching function and call it with the old
			# and new values
			self.__getattribute__(funcname)(old_value, value)
		# call the base/original __setattr__ function to handle
		# actually setting the value
		self.__org__setattr__(name, value)
		
	# replace the __setattr__ function with our new watching
	# enable version
	klass.__setattr__ = new__setattr__

Wow, such a beautiful piece of python magic. And this how it works:

wrapProperties(Foo)
def printIt(self, old_value, new_value):
	print 'old_value = %s, new_value = %s' % (old_value, new_value)
Foo.__set__v = printIt
f = Foo(5)
print f
f.v = 10
print f
f.bar = 'baz'

And the output is different now:

old_value = None, new_value = 5
Foo(v=5)
old_value = 5, new_value = 10
Foo(v=10)

Cool beans, we were able to see the value of f.v changing, even when the initializer set the value. And this is exactly what I wanted. I can now wrap my health variable with code that will update the GUI without polluting the logic code.