transparent proxy object
Alex A. Naanou
alex_nanou at pochtamt.ru
Wed Feb 26 16:22:34 EST 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