本文基于 python 3.6
题外话
先来看一个问题: 已知对于一个对象来说,运算符 > 会去调用对象的 __gt__ 方法:
已知对于对象来说,__getattribute__ 在寻找实例属性时被无条件调用:
那么根据亚里士多德的三段论来说,我们可以使用一个 override __getattribute__ 方法的对象去代理对象 t 的所有方法,实现一个简单的对象代理:
好像的确可行,但是
重新整理一下思路,> 对去调用对象的 __gt__ 方法,而 __getattribute__ 会去截获属性寻找的过程,返回 t 对象的 __gt__ 方法,所以这种问题应该是前提出现了偏差
根据错误信息可以知道 __getattribute__ 没起作用,翻阅文档可知
called unconditionally to implement attribute accesses for instances of the class. if the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an attributeerror. this method should return the (computed) attribute value or raise an attributeerror exception. in order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).
正题
那么如何正确地实现一个对象的代理呢?其实 __getattribute__ 也可以,不过要在 proxy 类中也显示的定义 __gt__ 等 special method。但是 __getattribute__ 在编程时要极为留意,避免 maximum recursion depth exceeded,还是 __getattr__ 更加 friendly
celery 源代码中有一个现成的实现(她自己声称是 stolen from werkzeug.local.proxy)
def _default_cls_attr(name, type_, cls_value):
# proxy uses properties to forward the standard
# class attributes __module__, __name__ and __doc__ to the real
# object, but these needs to be a string when accessed from
# the proxy class directly. this is a hack to make that work.
# -- see issue #1087.
def __new__(cls, getter):
instance = type_.__new__(cls, cls_value)
instance.__getter = getter
return instance
def __get__(self, obj, cls=none):
return self.__getter(obj) if obj is not none else self
return type(bytes_if_py2(name), (type_,), {
'__new__': __new__, '__get__': __get__,
})
class proxy(object):
proxy to another object.
# code stolen from werkzeug.local.proxy.
__slots__ = ('__local', '__args', '__kwargs', '__dict__')
def __init__(self, local,
args=none, kwargs=none, name=none, __doc__=none):
object.__setattr__(self, '_proxy__local', local)
object.__setattr__(self, '_proxy__args', args or ())
object.__setattr__(self, '_proxy__kwargs', kwargs or {})
if name is not none:
object.__setattr__(self, '__custom_name__', name)
if __doc__ is not none:
object.__setattr__(self, '__doc__', __doc__)
@_default_cls_attr('name', str, __name__)
def __name__(self):
try:
return self.__custom_name__
except attributeerror:
return self._get_current_object().__name__
@_default_cls_attr('qualname', str, __name__)
def __qualname__(self):
return self._get_current_object().__qualname__
@_default_cls_attr('module', str, __module__)
def __module__(self):
return self._get_current_object().__module__
@_default_cls_attr('doc', str, __doc__)
def __doc__(self):
return self._get_current_object().__doc__
def _get_class(self):
return self._get_current_object().__class__
@property
def __class__(self):
return self._get_class()
def _get_current_object(self):
get current object.
this is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
loc = object.__getattribute__(self, '_proxy__local')
if not hasattr(loc, '__release_local__'):
return loc(*self.__args, **self.__kwargs)
try: # pragma: no cover
# not sure what this is about
return getattr(loc, self.__name__)
except attributeerror: # pragma: no cover
raise runtimeerror('no object bound to {0.__name__}'.format(self))
def __dict__(self):
return self._get_current_object().__dict__
except runtimeerror: # pragma: no cover
raise attributeerror('__dict__')
def __repr__(self):
obj = self._get_current_object()
return ''.format(self.__class__.__name__)
return repr(obj)
def __bool__(self):
return bool(self._get_current_object())
return false
__nonzero__ = __bool__ # py2
def __dir__(self):
return dir(self._get_current_object())
return []
def __getattr__(self, name):
if name == '__members__':
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __delitem__(self, key):
del self._get_current_object()[key]
def __setslice__(self, i, j, seq):
self._get_current_object()[i:j] = seq
def __delslice__(self, i, j):
del self._get_current_object()[i:j]
def __setattr__(self, name, value):
setattr(self._get_current_object(), name, value)
def __delattr__(self, name):
delattr(self._get_current_object(), name)
def __str__(self):
return str(self._get_current_object())
def __lt__(self, other):
return self._get_current_object() < other
def __le__(self, other):
return self._get_current_object() <= other
# omit some special method
另外说一点,使用 _default_cls_attr 而不用 property 装饰器 可以参考 issue 1087
从 class 访问 property 会返回 property 的实例
原因貌似好像是这个 (依据等价实现)
所以 celery 自己搞了一个 descriptor
喜欢的话关注收藏评论转发比心么么哒!python学习交流群330637182内有大量的项目开发和新手教学视频五千人大群等着你来加入