Friday 28 January 2011

Proxying object access

Why are you reading this post? You are wasting valuable time where you could be writing my code for me! On that note, I often write the following code pattern. Anyone know of a library somewhere which has a variety of simple and decoupled helper code like this?

class ObjectWrapper(object):
def __init__(self, ob, attrWrapperClass=None):
self.__attr_wrapper__ = attrWrapperClass
self.__wrapped_object__ = ob

def __getattr__(self, attrName):
# This will trigger an attribute error, if it does not exist.
realValue = getattr(self.__wrapped_object__, attrName)
# We give them the attribute directly, if it is not callable.
if not callable(realValue):
return realValue
# Otherwise, all calls should be proxied and resources tracked appropriately.
attrWrapperClass = self.__attr_wrapper__
if attrWrapperClass is None:
attrWrapperClass = ObjectAttributeWrapper
return attrWrapperClass(self.__wrapped_object__, attrName)

class ObjectAttributeWrapper(object):
__bypass_attrnames__ = set()

def __init__(self, ob, obAttrName):
self.__wrapped_object__ = ob
self.__wrapped_attrname__ = obAttrName

@property
def __value__(self):
return getattr(self.__wrapped_object__, self.__wrapped_attrname__)

@property
def __context__(self):
return "%r.%s" % (self.__wrapped_object__, self.__wrapped_attrname__)

def __getattr__(self, subAttrName):
# Allow introspection.
if subAttrName in self.__bypass_attrnames__ or subAttrName.startswith("im_") or subAttrName.startswith("func_"):
return getattr(self.__value__, subAttrName)
return AttributeError("Not expecting to be asked about '%s' for '%r.%s'" % (subAttrName, self.__context__))

def __call__(self, *args, **kwargs):
""" Wrap the call of a method with stat tracking. """
with StatContext_NEVER_CHECK_THIS_IN(self.__context__):
return self.__value__(*args, **kwargs)
In the use case for this particular code, CPU usage and memory allocations within each call made on the wrapped instance are tagged with label comprised of the object representation and the function name. This is of course not the most ideal label to tag it with, but for the purpose of pasting here it is good enough.

For context, just this morning I used it in the following way:
class SomeObject(object):
def __init__(self):
self.log = logging.getLogger("blah")
try:
from . import stats
self.log = stats.ObjectWrapper(self.log)
except ImportError:
pass
With the following output:

2 comments:

  1. Does this work with types like int and float as well?

    ReplyDelete
  2. It would be quicker for you to try, than to ask and wait for an answer. In any case, I haven't tried. But I imagine that it should. Magic methods like __add__ and so forth, should be queried in the same way.

    ReplyDelete