Here we will demonstrate a technique that will make it possible to semi-transparently proxy an object and override/proxy methods or data including special methods like __call__. this module will define several general tools and components that can be used as a basis for creation of various proxy classes...
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 | #=======================================================================
__version__ = '''0.1.01'''
__sub_version__ = '''20040919004511'''
__copyright__ = '''(c) Alex A. Naanou 2003'''
#-----------------------------------------------------------------------
__doc__ = '''\
this module will define a set of utilities and classes to be used to build
various proxies...
'''
#-----------------------------------------------------------------------
import new
import types
import weakref
#-----------------------------------------------------------------------
#------------------------------------------------------getproxytarget---
def getproxytarget(obj):
'''
this will return the unproxied target object.
'''
ogetattribute = object.__getattribute__
try:
return ogetattribute(obj, ogetattribute(obj, '__proxy_target_attr_name__'))
except:
if not isproxy(obj):
raise TypeError, 'obj must be a proxy (got: %s).' % obj
raise
#-------------------------------------------------------------isproxy---
def isproxy(obj):
'''
this will return True if obj is a proxy object (relative to AbstractProxy).
NOTE: this will work only for the pli framework proxies that inherit
from AbstractProxy.
'''
return isinstance(obj, AbstractProxy)
#-----------------------------------------------------------------------
#-------------------------------------------------------AbstractProxy---
# this is here for:
# 1) use of isproxy.
# it is helpful if all *similar* classes be a subclass of one
# parent class for easy identification...
# 2) define the default configuration for use with the 'proxymethod'
# and helper functions...
class AbstractProxy(object):
'''
this is a base class for all proxies...
'''
__proxy_target_attr_name__ = 'proxy_target'
#----------------------------------------------------------BasicProxy---
class BasicProxy(AbstractProxy):
'''
this defines a nice proxy repr mixin.
'''
def __repr__(self):
'''
'''
ogetattribute = object.__getattribute__
return '<%s proxy at %s to %s>' % (ogetattribute(self, '__class__').__name__,
hex(id(self)),
repr(getproxytarget(self)))
#-----------------------------------------------------------------------
# this section defines component mix-ins...
#-----------------------------------------------------ComparibleProxy---
class ComparibleProxy(BasicProxy):
'''
proxy mixin. this will transfer the rich comparison calls directly
to the target...
'''
__proxy_target_attr_name__ = 'proxy_target'
# these cant be avoided without eval...
def __eq__(self, other):
return getproxytarget(self) == other
def __ne__(self, other):
return getproxytarget(self) != other
def __gt__(self, other):
return getproxytarget(self) > other
def __lt__(self, other):
return getproxytarget(self) < other
def __ge__(self, other):
return getproxytarget(self) >= other
def __le__(self, other):
return getproxytarget(self) <= other
#---------------------------------------------------------CachedProxy---
# NOTE: from here on all proxies are by default cached...
class CachedProxy(BasicProxy):
'''
this defaines the basic proxy cache manager functionality.
'''
# this may either be None or a dict-like (usualy a weakref.WeakKeyDictionary)
# if None the proxy caching will be disabled
__proxy_cache__ = None
def __new__(cls, source, *p, **n):
'''
return the cached proxy or create a one and add it to cache.
'''
res = cls._getcached(source)
if res == None and hasattr(cls, '__proxy_cache__') \
and cls.__proxy_cache__ != None:
obj = super(CachedProxy, cls).__new__(cls, source, *p, **n)
cls._setcache(source, obj)
return obj
return super(CachedProxy, cls).__new__(cls, source, *p, **n)
@classmethod
def _getcached(cls, source):
'''
get an object from cache.
if the object is not in cache or cache is disabled None will be returned.
'''
if hasattr(cls, '__proxy_cache__') and cls.__proxy_cache__ != None \
and source in cls.__proxy_cache__:
return cls.__proxy_cache__[source]
return None
@classmethod
def _setcache(cls, source, obj):
'''
add an object to cache.
'''
if hasattr(cls, '__proxy_cache__') and cls.__proxy_cache__ != None:
cls.__proxy_cache__[source] = obj
#-----------------------------------------------------------------------
# this section defines ready to use base proxies...
#---------------------------------------------InheritAndOverrideProxy---
# this is the Proxy cache...
_InheritAndOverrideProxy_cache = weakref.WeakKeyDictionary()
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# this works as follows:
# 1) create a class that inherits from InheritAndOverrideProxy and the
# proxied objects class...
# reference code:
# class local_proxy(InheritAndOverrideProxy, proxied.__class__):
# pass
# 2) creates an object from the above class, and sets its __dict__ to
# reference the target objects __dict__. thus enabling setting and
# referencing data to the proxied object....
#
class InheritAndOverrideProxy(CachedProxy):
'''
this is a general (semi-transparent) proxy.
'''
# this defines the attribute name where the proxy target is
# stored...
__proxy_target_attr_name__ = 'proxy_target'
# this is used to generate unique proxy class names...
__proxy_count__ = 0
# this may either be None or a dict-like (usualy a weakref.WeakKeyDictionary)
# if None the proxy caching will be disabled
__proxy_cache__ = _InheritAndOverrideProxy_cache
def __new__(cls, source, *p, **n):
'''
this will create a proxy, wrap the target and return the result...
else return the target.
'''
osetattr = object.__setattr__
cls_name = cls.__name__
try:
# process proxy cache...
_obj = cls._getcached(source)
if _obj != None:
return _obj
# create an object of a class (also just created) inherited
# from cls and source.__class__
_obj = object.__new__(new.classobj('',(cls, source.__class__), {}))
# get the new class....
cls = object.__getattribute__(_obj, '__class__')
# name the new class...
# NOTE: the name may not be unique!
cls.__name__ = cls_name + '_' + str(cls.__proxy_count__)
cls.__proxy_count__ += 1
# considering that the class we just created is unique we
# can use it as a data store... (and we do not want to
# pollute the targets dict :) )
setattr(cls, cls.__proxy_target_attr_name__, source)
# replace the dict so that the proxy behaves exactly like
# the target...
osetattr(_obj, '__dict__', source.__dict__)
# we fall here in case we either are a class constructor, function or a callable....
# WARNING: this might be Python implementation specific!!
except (TypeError, AttributeError), e:
# function or callable
if type(source) in (types.FunctionType, types.LambdaType, \
types.MethodType, weakref.CallableProxyType):
# callable wrapper hook...
if hasattr(cls, '__proxy_call__') and cls.__proxy_call__ != None:
return cls.__proxy_call__(source)
return source
# class (nested class constructors...)
elif callable(source):
# class wrapper hook...
if hasattr(cls, '__proxy_class__') and cls.__proxy_class__ != None:
return cls.__proxy_class__(source)
return source
return source
# process proxy cache...
cls._setcache(source, _obj)
return _obj
# this is here to define the minimal __init__ format...
# WARNING: there is a danger to call the targets __init__ so
# keep this empty!!!
def __init__(self, source, *p, **n):
pass
# these two methods are the optional function and class wrappers...
## def __proxy_call__(self, target):
## return target
## def __proxy_class__(self, target):
## return target
#-----------------------------------TranparentInheritAndOverrideProxy---
# this is the Proxy cache...
_TranparentInheritAndOverrideProxy_cache = weakref.WeakKeyDictionary()
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class TranparentInheritAndOverrideProxy(InheritAndOverrideProxy, ComparibleProxy):
'''
this is a transparent variant of InheritAndOverrideProxy. its' behavior
is in no way different from the proxied object.
NOTE: due to the fact that this explicitly proxies the __getattribute__
and __setattr__ calls, it is slower then the semi-transparent
variant.
'''
__proxy_target_attr_name__ = 'proxy_target'
__proxy_count__ = 0
__proxy_cache__ = _TranparentInheritAndOverrideProxy_cache
# this defines the attributes that are resolved to the proxy itself
# (not the target object)...
__proxy_public_attrs__ = (
'__proxy_call__',
'__proxy_class__',
)
def __getattribute__(self, name):
'''
'''
ogetattribute = object.__getattribute__
if name in ogetattribute(self, '__proxy_public_attrs__') \
+ (ogetattribute(self, '__proxy_target_attr_name__'),):
return super(TranparentInheritAndOverrideProxy, self).__getattribute__(name)
return self.proxy_target.__getattribute__(name)
#=======================================================================
if __name__ == '__main__':
# Examples:
#
# here we create a class we will use as a target for our proxy...
# this will define same special methods, normal methods and
# attributes (both class and object)...
class O(object):
class_attr = 'some value...'
def __init__(self):
self.obj_attr = 1234567
def __call__(self):
print 'O object (__call__)! (', self.__class__, hex(id(self)), ').'
def meth(self, arg):
print 'O object (meth)! (', self.__class__, hex(id(self)), ').'
# create an instance of the above...
o = O()
# now the fun starts..
# we define a proxy that will intercept calls to the target object.
class Proxy(TranparentInheritAndOverrideProxy):
def __call__(self, *p, **n):
print 'Proxy:\n\t',
self.proxy_target(*p, **n)
# bind a proxy to the target...
p = Proxy(o)
# call the original...
o()
# call the proxy...
p()
# raw access attributes...
print p.obj_attr
print p.class_attr
# set attributes via the proxy...
p.xxx = 'xxx value...'
p.obj_attr = 7654321
# access new values attributes...
print o.xxx
print o.obj_attr
print o.class_attr
# print the class of the proxy and the target...
print 'o.__class__ is', o.__class__
print 'p.__class__ is', p.__class__
# compare the two...
print 'o == p ->', o == p
# isproxy tests...
print 'p is a proxy test ->', isproxy(p)
print 'o is a proxy test ->', isproxy(o)
# print a nice repr...
print 'o is', o
print 'p is', p
# now we test the cache...
# create several proxies to the same object....
p0 = Proxy(o)
p1 = Proxy(o)
# test if they are the same...
print p is p0, p0 is p1
#=======================================================================
# vim:set ts=4 sw=4 nowrap expandtab:
|
the goals here were: 1) speed -- the proxy should introduce minimal overhead to non-proxied functionality. 2) transparency -- the surroundings should not notice the presence of the proxy.
the way this works is when we wrap an object with the proxy class, this class actually does the following: - create a hybrid class which subclasses both the proxy and the objects class. this will enable attribute and method access via the default python MRO mechanisms... - create an object of this new class and replace its __dict__ with the target objects (e.g. the two objects will share the same state).
the above two actions are done in the InheritAndOverrideProxy class. this makes for fast attribute access as the same mechanisms are used as in any other python object while maintaining some transparency. but here goal #2 is not fully accomplished, this is because the proxy classes attributes override the target classes (e.g. the proxies __class__ attribute resolves to the combination class not the targets class). and the comparison of the proxy will differ from the original.
to solve the comparison problem one could use the ComparibleProxy as a mixin. but the only way (at least that I could think of :) ) to solve the class attribute problem is to use __getattribute__ which is relatively straight forward and shown in the TranparentInheritAndOverrideProxy class (the payoff here is the speed drop due to the use of __getattribute__).
NOTE: all the proxies here are cached, enabling reuse of proxies... NOTE: all proxies here are single level proxies. in some cases one would need a recursive proxy which is quite easy to make using the __getattribute__ to wrap the next node... NOTE: this can be easily used with Py2.3... just replace the decorators with the traditional classmethod dance... (not tested with python versions <2.2).
for a full version of this module and a recursive proxy example see the pli.pattern.proxy.generic module in the pli python library (http://pli.sf.net/)