transparent proxy object

Alex A. Naanou alex_nanou at pochtamt.ru
Wed Feb 26 22:22:34 CET 2003


Task:
  "transparently" intercept/cache all calls to the object, all its
attributes and sub-objects while maintaining full object
functionality.

The Answer:
  here is the answer I came up with, though it fits the task
semanticly and I like how it works in concept, the application is
rather time-critical thus this code does not fit the job...
  does any one have an idea how to implement something with the same
semantics but more efficiently (I'm intrested in a pure Python
solution, not a C/C++ extention type)??      ...Thanks.

Code and example of use follow:

---cut---

__version__ = '''0.3.00'''
__sub_version__ = '''200302270015'''
__copyright__ = '''(c) Alex A. Naanou 2003'''

import new
import types
import weakref

#-----------------------------------------------------------------------
# a utill curry function
curry = lambda func, *pargs, **nargs:\
			lambda *p, **n:\
				apply(func, pargs + p, dict(nargs.items() + n.items()))


#-----------------------------------------------------------------------
# the main workhorse
class callproxy(object):
	'''
	proxy to the object, intercept and curry call calls to a queue.
	'''
	def __new__(cls, obj, ext_queue_lst=None, proxy_cache=None,
drop_refs=0, callback=None):
		'''
		construct an appropriate class and return its object, the class is
to be a
		subclass of callproxy and the objs' class, if this is to prove
impossible return
		the original obj or a curried __queuecall__ if the obj is callable.

		Parameters:
			obj				: the proxied object.
			ext_queue_lst	: list to be user as external queue.
			proxy_cache		: optional dict to be used as proxy cache 
							  NOTE: no cache management is currently done in this class.
			drop_refs		: if true raise TypeError trying to get a proxied
objects value.
			                  NOTE: this does not prevent setting the proxied
objects value.
			callback		: a callable that gets called on proxy call.
			                  NOTE: this will receive the called object as the
first argument,
							        and all the arguments (if any).

		NOTE: either one or both ext_queue_lst or callback must be
specified.
		NOTE: to keep the obj.__dict__ intact the classes' namespace is used
for
		      all instance data (this is not a problem as a new class is
constructed
		      for each callproxy instance).
		'''
		## do input tests
		# callback test
		if callback != None:
			if not callable(callback):
				raise TypeError, '"callback" must be callable.'
		elif ext_queue_lst == None:
			raise TypeError, 'one of either callback or ext_queue_lst must be
specified.'
		# test if this is mutable
		elif not hasattr(ext_queue_lst, 'append'):
			raise TypeError, '"ext_queue_lst" have an "append" method.'
		# test if this supports dict interface & mutable
		if proxy_cache != None and not hasattr(proxy_cache, '__setitem__')
and not hasattr(proxy_cache, '__getitem__'):
			raise TypeError, 'cache must be of type dict (given: ' +
str(type(proxy_cache)) + ')'
		# see if obj is in pool
		if None not in (proxy_cache, callback) and obj in
proxy_cache.keys():
			proxy = object.__getattribute__(proxy_cache[obj], '__class__')
			try:
				if drop_refs and proxy.__drop_refs != drop_refs:
					raise TypeError, 'proxy cache type mismatch (drop_refs option
inconsistent)'
				if callback and proxy.__callback != callback:
					raise TypeError, 'proxy cache type mismatch (callback option
inconsistent)'
			except AttributeError:
				return proxy_cache[obj]
		## start work...
		try:
			# this is quite explicit to avoid errors from misuse
			_obj = object.__new__(new.classobj('',(callproxy, obj.__class__),
{}))
			cls = object.__getattribute__(_obj, '__class__')
			# we have a hungry __setattr__ lurking...  :)
			osetattr = object.__setattr__
			osetattr(_obj, '__dict__', obj.__dict__)
			osetattr(cls, '_callproxy__obj', obj)
			osetattr(cls, '_callproxy__ext_queue_lst', ext_queue_lst)
			osetattr(cls, '_callproxy__cache', proxy_cache)
			osetattr(cls, '_callproxy__drop_refs', drop_refs)
			osetattr(cls, '_callproxy__callback', callback)
			if proxy_cache != None:
				proxy_cache[obj] = _obj
		except (TypeError, AttributeError):
			# function or callable
			if type(obj) in (types.FunctionType, types.LambdaType,
types.MethodType, weakref.CallableProxyType):
				if callback != None:
					if proxy_cache != None:
						proxy_cache[obj] = curry(callback, obj)
						return proxy_cache[obj]
					return curry(callback, obj)
				if proxy_cache != None:
					proxy_cache[obj] = curry(cls.__queuecall__, obj, ext_queue_lst)
					return proxy_cache[obj]
				return curry(cls.__queuecall__, obj, ext_queue_lst)
			# class (nested class constructors...)
			elif callable(obj):
				return obj
			# not callable and drop_refs is set
			elif drop_refs != 0:
				raise TypeError, 'can not reference a proxied object! (drop_refs
option is set).'
			return obj
		return _obj
	def __init__(self, *p, **n):
		'''
		dummy init....
		this is here so as to not call the proxied objects' __init__
		'''
		pass
	def __getattribute__(self, name):
		'''
		return a proxy to self.name
		'''
		if name == '__queuecall__':
			return object.__getattribute__(self, '__queuecall__')
		# NOTE: do not use "self.__class__" as a constructor as this will
result in namespace/inheritance pile-up!!
		# do a little name caching
		ogetattr = object.__getattribute__
		cls = ogetattr(self, '__class__')
		return callproxy(getattr(ogetattr(cls, '_callproxy__obj'), name),\
							ogetattr(cls, '_callproxy__ext_queue_lst'),\
							ogetattr(cls, '_callproxy__cache'),\
							ogetattr(cls, '_callproxy__drop_refs'),\
							ogetattr(cls, '_callproxy__callback'))
	def __setattr__(self, name, val):
		'''

		nothing special here... the usual __setattr__ semantics.
		'''
		cls = object.__getattribute__(self, '__class__')
		obj = object.__getattribute__(cls, '_callproxy__obj')
		setattr(obj, name, val)
	def __queuecall__(obj, ext_queue_lst, *pargs, **nargs):
		'''
		enqueue an obj call to ext_queue_lst.
		'''
		# capture the args...
		ext_queue_lst.append(lambda:apply(obj, pargs, nargs))
	__queuecall__ = staticmethod(__queuecall__)
	def __call__(self, *pargs, **nargs):
		'''
		call proxy.
		'''
		ogetattr = object.__getattribute__
		cls = ogetattr(self, '__class__')
		obj = ogetattr(cls, '_callproxy__obj')
		# sanity check!
		if not callable(obj):
			# raise an error (disguised as the original!! :) )
			obj(pargs, nargs)
		# NOTE: it is not forbidden to have a queue and a callback at the
same time
		callback = ogetattr(cls, '_callproxy__callback')
		if callback != None:
			apply(callback, (obj,) + pargs, nargs)
		queue = ogetattr(cls, '_callproxy__ext_queue_lst')
		if queue != None:
			#__doc__ = self.__class__.__obj.__call__.__doc__
			apply(self.__queuecall__, (obj, queue) + pargs, nargs)
		return


#-----------------------------------------------------------------------
##examles

if __name__ == '__main__':
	class X(object):
		cval = 100
		def __init__(self):
			self.c0 = self.c0()
			self.c1 = self.c1()
			self.ival = 9
		class c0(object):
			def __call__(self):
				print '$$$ c0'
		class c1(object):
			def f(self):
				print '$$$ c1.f'
		class c2(object):
			pass
		def f(self):
			print '$$$ f'
		def __call__(self):
			print '$$$ xx'

	x = X()

	q = []
	cache = {}

	
	xx = callproxy(x, q)
##	xx = callproxy(x, q, proxy_cache=cache)

	print '\ntesting init of proxied class'
	y = xx.c2()
	print y
	z = xx.__class__.c0()
	print z

	print '\nqueuing..'
	xx()
	xx.f()
	xx.c0()
	xx.c1.f()
	m = xx.c1
	m.f()

	print '\ntesting vars...'

	print x.cval, xx.cval
	xx.cval = 10000000
	print x.cval, xx.cval

	print x.ival, xx.ival
	xx.ival = 99999999
	print x.ival, xx.ival

	print '\nrunning queue...'
	print len(q)
	for f in q:
		print 'calling',
		f()

	def f(l):
		for i in range(l):
			xx.c1.f()


	l = 1000

	q = []
	cache = {}
	xx = callproxy(x, q)
	print '\nrunning call loop (noncached)... (len = ' + str(l) + ')'
	f(l)
	print 'queue length:', len(q)
	print 'cache length:', len(cache)
	

	q = []
	cache = {}
	xx = callproxy(x, q, proxy_cache=cache)
	print '\nrunning call loop (cached)... (len = ' + str(l) + ')'
	f(l)
	print 'queue length:', len(q)
	print 'cache length:', len(cache)



	print '\n\nrunning callback (sync) interface test...'
	def callback(obj, *p, **n):
		print 'callback: apply(', obj, ',', p, ',', n, ') --> ',
		apply(obj, p, n)

	# test callback feture
	xx = callproxy(x, callback=callback)
	xx.c0()
	xx.c1.f()



	# test drop_refs option (must raise a TypeError on xx.cval)
##	xx = callproxy(x, q, drop_refs=1)
##	xx.c1.f()
##	print x.cval, xx.cval
	



---uncut---




More information about the Python-list mailing list