Ok, seems like one never stops learning, so thanks for informing me.
You don’t need to enumerate all possible dunders.
Instead, use a namespace of methods bound to the proxied object:
def proxy(obj):
class ProxyMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
return {name: getattr(obj, name) for name in dir(type(obj))}
class Proxy(metaclass=ProxyMeta):
pass
return object.__new__(Proxy)
or:
def proxy(obj):
return object.__new__(
type(
'Proxy',
(),
{name: getattr(obj, name) for name in dir(type(obj))}
)
)
d = {"a": 1}
p = proxy(d)
print(p["a"]) # 1
d["a"] = 2
print(p["a"]) # 2
p["a"] = 3 # write-back works too
print(d["a"]) # 3
4 Likes
Here’s a little more versatile solution:
def make_forward(name):
if name in {"__new__", "__init__", "__getattribute__"}:
return getattr(object, name)
@property
def forward(self):
return self.__getattr__(name)
return forward
proxies = {}
def proxy(obj):
try:
ProxyMeta, Proxy = proxies[type(obj)]
except KeyError:
class ProxyMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
return {name: make_forward(name) for name in dir(type(obj))}
class Proxy(metaclass=ProxyMeta):
def __init__(self, target):
super().__setattr__("_target", target)
def __getattr__(self, name):
print(type(self), name)
return getattr(super().__getattribute__("_target"), name)
proxies[type(obj)] = ProxyMeta, Proxy
return Proxy(obj)
>>> d = {"a": 1}
>>> p = proxy(d)
>>> p["a"]
<class '__main__.proxy.<locals>.Proxy'> __getitem__
1
1 Like
Note that the above code prevents any customization of the proxy object, since even the __getattribute__ method is bound to the proxied object. So if the Proxy class is defined with a custom method such as:
class Proxy(metaclass=ProxyMeta):
def method(self):
print(4)
then an AttributeError would be raised from trying to access method from the proxy because the method doesn’t exist in the proxied object:
d = {"a": 1}
p = proxy(d)
p.method() # AttributeError: 'dict' object has no attribute 'method'
So it’s likely more desirable to wrap __getattribute__ with a function that tries getting the requested attribute from the proxy object first before falling back to the proxied object:
def proxy(obj):
def __getattribute__(self, name):
try:
return object.__getattribute__(proxy_obj, name)
except AttributeError:
return obj.__getattribute__(name)
class ProxyMeta(type):
@classmethod
def __prepare__(metacls, name, bases):
ns = {name: getattr(obj, name) for name in dir(type(obj))}
ns['__getattribute__'] = __getattribute__
return ns
class Proxy(metaclass=ProxyMeta):
def method(self):
print(4)
return (proxy_obj := object.__new__(Proxy))
d = {"a": 1}
p = proxy(d)
print(p["a"]) # 1
d["a"] = 2
print(p["a"]) # 2
p["a"] = 3
print(d["a"]) # 3
p.method() # 4