Saturday, 31 August 2013

How to implement lightweight "tag" wrapper class?

How to implement lightweight "tag" wrapper class?

I'm looking for a way to implement a universal lightweight wrapper class,
whose sole purpose is to "tag" arbitrary objects. (Hence
isinstance(wrapped_instance, WrapperClass) would return True, but in every
other respect, the enclosing "wrapper instance" would behave exactly as
the underlying "wrapped instance".)
One strategy for doing this would be to implement perfect (or
near-perfect) "pass-through delegation". IOW, each instance of the wrapper
class would store the wrapped instance (to serve as "delegate") in an
instance variable, and forward all (or rather, almost all) method calls
and instance-variable access to this delegate. (Notable exceptions to this
blanket pass-through policy would be any access to the wrapped instance
itself, and access to the __class__ property of the wrapper instance.)
Here's an attempt at doing this:
class Wrapper(object):
__slots__ = '_obj'
def __init__(self, obj):
super(Wrapper, self).__setattr__('_obj', obj)
def __getattr__(self, attr):
return getattr(self._obj, attr)
def __setattr__(self, attr, value):
return setattr(self._obj, attr, value)
At first, it seems to work. Notably, isinstance produces the desired result:
In [100]: wrapped_list = Wrapper(range(7))
In [101]: isinstance(wrapped_list, Wrapper)
Out[101]: True
But also, the standard methods of the wrapped instance seem to work:
In [102]: wrapped_list.pop()
Out[102]: 6
In [103]: wrapped_list.reverse(); wrapped_list.pop()
Out[103]: 0
All is not well, however. For example, len fails on the wrapped instance:
In [104]: len(wrapped_list)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-104-e9ec371eafc5> in <module>()
----> 1 len(wrapped_list)
TypeError: object of type 'Wrapper' has no len()
...even though pass-through seems to work fine for the __len__ method:
In [105]: wrapped_list.__len__()
Out[105]: 5
With repr the situation is, in some sense, worse, since not even
explicitly accessing the wrapper instance's __repr__ method does the right
thing:
In [106]: wrapped_list
Out[106]: <__main__.Wrapper at 0x10851a360>
In [107]: wrapped_list.__repr__()
Out[107]: '<__main__.Wrapper object at 0x10851a360>'
In [108]: wrapped_list._obj
Out[108]: [5, 4, 3, 2, 1]
Most disturbingly, basic indexing fails:
In [109]: wrapped_list[2]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-109-424990b88e32> in <module>()
----> 1 wrapped_list[2]
TypeError: 'Wrapper' object does not support indexing
The problems shown in the example above are somewhat specific to the
example. I expect to see a different constellation of failures if I were
to test a wrapped dict() or a wrapped set(), etc.
Is there some way to achieve this effect?

No comments:

Post a Comment