Welcome, guest | Sign In | My Account | Store | Cart

transparent object proxying for (almost) all objects.

Python, 165 lines
  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
class Proxy(object):
    __slots__ = ["_obj", "__weakref__"]
    def __init__(self, obj):
        object.__setattr__(self, "_obj", obj)
    
    #
    # proxying (special cases)
    #
    def __getattribute__(self, name):
        return getattr(object.__getattribute__(self, "_obj"), name)
    def __delattr__(self, name):
        delattr(object.__getattribute__(self, "_obj"), name)
    def __setattr__(self, name, value):
        setattr(object.__getattribute__(self, "_obj"), name, value)
    
    def __nonzero__(self):
        return bool(object.__getattribute__(self, "_obj"))
    def __str__(self):
        return str(object.__getattribute__(self, "_obj"))
    def __repr__(self):
        return repr(object.__getattribute__(self, "_obj"))
    
    #
    # factories
    #
    _special_names = [
        '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__', 
        '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__', 
        '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__', 
        '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__',
        '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__', 
        '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__', 
        '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', 
        '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', 
        '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', 
        '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', 
        '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__', 
        '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', 
        '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__', 
        '__truediv__', '__xor__', 'next',
    ]
    
    @classmethod
    def _create_class_proxy(cls, theclass):
        """creates a proxy for the given class"""
        
        def make_method(name):
            def method(self, *args, **kw):
                return getattr(object.__getattribute__(self, "_obj"), name)(*args, **kw)
            return method
        
        namespace = {}
        for name in cls._special_names:
            if hasattr(theclass, name):
                namespace[name] = make_method(name)
        return type("%s(%s)" % (cls.__name__, theclass.__name__), (cls,), namespace)
    
    def __new__(cls, obj, *args, **kwargs):
        """
        creates an proxy instance referencing `obj`. (obj, *args, **kwargs) are
        passed to this class' __init__, so deriving classes can define an 
        __init__ method of their own.
        note: _class_proxy_cache is unique per deriving class (each deriving
        class must hold its own cache)
        """
        try:
            cache = cls.__dict__["_class_proxy_cache"]
        except KeyError:
            cls._class_proxy_cache = cache = {}
        try:
            theclass = cache[obj.__class__]
        except KeyError:
            cache[obj.__class__] = theclass = cls._create_class_proxy(obj.__class__)
        ins = object.__new__(theclass)
        theclass.__init__(ins, obj, *args, **kwargs)
        return ins


------ example ------
>>> p = Proxy(6)
>>> p
6
>>> type(p)
<class '__main__.Proxy(int)'>
>>> p + 2
8
>>> p2 = Proxy([1,2,3])
>>> p2
[1, 2, 3]
>>> dir(p2)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__ #...
'__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__ini #...
educe__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', ' #...
'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>> isinstance(p2, list)
True
>>> p2.append(8)
>>> p2.append(2)
>>> p2.append(5)
>>> p2
[1, 2, 3, 8, 2, 5]
>>> p2.sort()
>>> p2
[1, 2, 2, 3, 5, 8]
>>> p2[2]
2
>>> p2[-1]
8
>>> type(p2)
<class '__main__.Proxy(list)'>
>>> p2.__class__
<type 'list'>

----- exceptions -----
Proxying user-types should work perfectly well. But proxying builtin objects,
like ints, floats, lists, etc., has some limitation and inconsistencies,
imposed by the interpreter:

>>> p = Proxy(6)
>>> p + p
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'Proxy(int)' and 'Proxy(int)'


>>> Proxy([1,2,3]) + [4,5]
[1, 2, 3, 4, 5]
>>> Proxy([1,2,3]) + Proxy([4,5])
>>> p = Proxy([1,2,3])
>>> p.extend(Proxy([4,5]))
>>> p
[1, 2, 3, 4, 5]
>>> p + Proxy([6, 7])
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "Proxy.py", line 49, in method
    return getattr(object.__getattribute__(self, "_obj"), name)(*args, **kw)
TypeError: can only concatenate list (not "Proxy(list)") to list


Also note that the methods of a proxied type return "real objects", not 
proxies. So, 

>>> p = Proxy(3)
>>> type(p)
<class '__main__.Proxy(int)'>
>>> p + 1
4
>>> type(_)
<type 'int'>
>>> p += 1
>>> p
4
>>> type(p)
<type 'int'>

In this case, 'p' was reassigned a real integer, and the proxy was
garbage-collected. What you might want to do is

>>> p = Proxy(3)
>>> p = Proxy(p + 1)
>>> p
4
>>> type(p)
<class '__main__.Proxy(int)'>

Object proxying is an important and useful concept in many places. Proxying, but itself, is not very useful... obj and Proxy(obj) should behave the same. The necessity of proxying comes to realization when you want Proxy(obj) to behave slightly different from obj. You can do it but subclassing the base Proxy class.

As an example for useful proxying, see my ShelfProxy recipe. In this recipe, shelf[key] returns a proxy, not the real object, and the object is serialized back into the shelf when deleted.

3 comments

Robert K 17 years, 4 months ago  # | flag

2 bugs. (1) in function "_create_class_proxy": replace line "if hasattr(theclass, name):" with "if hasattr(theclass, name) and not hasattr(cls, name):".

(2) in function "__new__": remove line "theclass.__init__(ins, obj, args, *kwargs)" because it will be done by python itself becouse isinstance(ins, cls)

Matthias Urlichs 12 years ago  # | flag

(3) __rfloorfiv__ instead of *div

(4) __unicode__ should be coded analogous to __str__

Avi Shukron 7 years, 8 months ago  # | flag

Bug: You are missing a special case for __hash__. The following method is missing:

def __hash__(self):
    return hash(object.__getattribute__(self, "_obj"))

And you also need to remove __hash__ from _special_names.

This bug prevents you from using a proxy of class as a dict key:

{Proxy(6): "asdfsadf"}  # This works just fine

class A(object):
    pass

p = Proxy(A)
{p: "asdasd"}  # Crashes

TypeError: descriptor '__hash__' of 'object' object needs an argument
Created by tomer filiba on Fri, 26 May 2006 (PSF)
Python recipes (4591)
tomer filiba's recipes (12)

Required Modules

  • (none specified)

Other Information and Tasks