views:

394

answers:

6

I am wanting to completely wrap an object so that all attribute and method requests get forwarded to the object it's wrapping, but also overriding any methods or variables that I want, as well as providing some of my own methods. This wrapper class should appear 100% as the existing class (isinstance must act as if it is actually the class), however subclassing in itself is not going to cut it, as I want to wrap an existing object. Is there some solution in Python to do this? I was thinking something along the lines of:

class ObjectWrapper(BaseClass):
    def __init__(self, baseObject):
        self.baseObject = baseObject

    def overriddenMethod(self):
        ...

    def myOwnMethod1(self):
        ...

    ...

    def __getattr__(self, attr):
        if attr in ['overriddenMethod', 'myOwnMethod1', 'myOwnMethod2', ...]
            # return the requested method
        else:
            return getattr(self.baseObject, attr)

But I'm not that familiar with overriding __getattr__, __setattr__ and __hasattr__, so I'm not sure how to get that right.

A: 

__getattr__ is only called for things that Python can't find in __dict__, so you don't have to worry about overriddenMethod, etc.

For more details, see section 5 "Proxy" in this article.

Aaron Digulla
That article predates "new"-style types, and so is not a good implementation reference on any version of Python since about 2001.
Joe
+3  A: 

__getattr__ has the advantage that it's only called when the attribute does not exist, so you should not need an explicit list -- anything you don't define will automatically get proxied.

__setattr__ is trickier because it's always called. Make sure you use a superclass call or object.__setattr__ when setting your own attributes; using setattr() within __setattr__ will cause infinite recursion.

The final bit, affecting isinstance, is very difficult. You can do it with an assigment to your wrapper instance's .__class__ variable (but this also overrides class dictionary resolution order), or by dynamically constructing your wrapper type using a metaclass. Since isinstance is so rare in Python code, it seems overkill to actually try to trick it.

More information on special attribute access methods.

Even more information on them, plus some help with metaclasses.

Joe
A: 

By 'existing object' you mean an instance of another class? Sounds to me as though you just need to inherit from the base class. When you create your new object, pass in the details of the base object, or add a method in your new class which copies the data of the base class instance into itself.

Jon Cage
If only it were that simple. The class I'm wrapping is a third-party one, and I believe it's written in C. Makes it a whole heap harder (and it could change when we update to a new version of the third-party library, and I'd rather not then have to change our code too.)
Smashery
I had a feeling that might be the case.. so basically you want to create an interface to an existing class. In which case, your suggestion is probably a neat way of going about it. Good luck :-)
Jon Cage
+1  A: 

Start with this and mess with stuff in the loop to suit your needs:

import inspect
class Delegate(object):
    def __init__(self, implementation):
        self.__class__ = implementation.__class__
        for n, m in inspect.getmembers(implementation, callable):
            if not n.startswith('_'):
                setattr(self, n, m)

The ability to wrap not-new-style-objects is left as an exercise to the reader :)

Henrik Gustafsson
+1  A: 

See my answer to this question.

Paul McGuire
+5  A: 

The simplest way in most cases is probably:

class ObjectWrapper(BaseClass):
    def __init__(self, baseObject):
        self.__class__ = type(baseObject.__class__.__name__,
                              (self.__class__, baseObject.__class__),
                              {})
        self.__dict__ = baseObject.__dict__

    def overriddenMethod(self):
        ...

Working in this way, i.e. by reassigning self's __class__ and __dict__ in this fashion, you need only provide your overrides -- Python's normal attribute getting and setting mechanisms will do the rest... mostly.

You'll be in trouble only if baseObject.__class__ defines __slots__, in which case the multiple inheritance approach doesn't work and you do need the cumbersome __getattr__ (as others said, at least you don't need to worry that it will be called with attributes you're overriding, as it won't!-), __setattr__ (a greater pain, as it DOES get called for every attribute), etc; and making isinstance and special methods work takes painstaking and cumbersome detailed work.

Essentially, __slots__ means that a class is a special, each instance a lightweight "value object" NOT to be subject to further sophisticated manipulation, wrapping, etc, because the need to save a few bytes per instance of that class overrides all the normal concerns about flexibility and so on; it's therefore not surprising that dealing with such extreme, rare classes in the same smooth and flexible way as you can deal with 99%+ of Python objects is truly a pain. So DO you need to deal with __slots__ (to the point of writing, testing, debugging and maintaining hundreds of lines of code just for those corner cases), or will the 99% solution in half a dozen lines suffice?-)

Alex Martelli