transparent object proxying for (almost) all objects.
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.
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)
(3) __rfloorfiv__ instead of *div
(4) __unicode__ should be coded analogous to __str__
Bug: You are missing a special case for
__hash__
. The following method is missing:And you also need to remove
__hash__
from_special_names
.This bug prevents you from using a proxy of class as a dict key: