你想将某个实例的属性访问代理到内部另一个实例中去,目的可能是作为继承的一个替代方法或者实现代理模式。
class A:def spam(self, x):print('This is A spam is:', x)def foo(self):print('This is A foo')class B1:'''简单代理'''def __init__(self):self._a = A()def spam(self, x):return self._a.spam(x)def foo(self):return self._a.foo()def bar(self):passclass B2:'''使用 __getattr__ 的代理, 代理方法比较多的时候'''def __init__(self):self._a = A()def bar(self):print('This is B2 bar')def __getattr__(self, name):'''这个方法在访问 attribute 不存在的时候被调用'''return getattr(self._a, name)
b = B2()b.bar()b.spam(42)b.foo()This is B2 barThis is A spam is: 42This is A foo
1. 代理模式
class Proxy:def __init__(self, obj):self._obj = objdef __getattr__(self, name):print('getattr', name)return getattr(self._obj, name)def __setattr__(self, name, value):if name.startswith('_'):super().__setattr__(name, value)else:print('setter:', name, value)setattr(self._obj, name, value)def __delattr__(self, name):if name.startswith('_'):super().__delattr__(name)else:print('delattr', name)delattr(self._obj, name)class Spam:def __init__(self, x):self.x = xdef bar(self, y):print('Spam.bar', self.x, y)
s = Spam(2)p = Proxy(s)print(p.x)p.bar(3)p.x = 37getattr x2getattr barSpam.bar 2 3setter: x 37
2. 继承的替代方案
class A:def spam(self, x):print('A.spam', x)def foo(self):print('A.foo')class B:def __init__(self):self._a = A()def spam(self, x):print('B.spam', x)self._a.spam(x)def bar(self):print('B.bar')def __getattr__(self, name):return getattr(self._a, name)b = B()b.spam(3)b.bar()b.foo()B.spam 3A.spam 3B.barA.foo
当实现代理模式时,还有些细节需要注意。 首先,__getattr__() 实际是一个后备方法,只有在属性不存在时才会调用。 因此,如果代理类实例本身有这个属性的话,那么不会触发这个方法的。 另外,__setattr__() 和 __delattr__() 需要额外的魔法来区分代理实例和被代理实例 _obj 的属性。 一个通常的约定是只代理那些不以下划线 _ 开头的属性 (代理类只暴露被代理类的公共属性)。
还有一点需要注意的是,__getattr__() 对于大部分以双下划线 (__) 开始和结尾的属性并不适用。 比如,考虑如下的类:
class ListLike:"""__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义"""def __init__(self):self._items = []def __getattr__(self, name):return getattr(self._items, name)
如果是创建一个 ListLike 对象,会发现它支持普通的列表方法,如 append () 和 insert (), 但是却不支持 len ()、元素查找等。例如:
>>> a = ListLike()>>> a.append(2)>>> a.insert(0, 1)>>> a.sort()>>> len(a)Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: object of type 'ListLike' has no len()>>> a[0]Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: 'ListLike' object does not support indexing>>>
为了让它支持这些方法,你必须手动的实现这些方法代理:
class ListLike:"""__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义"""def __init__(self):self._items = []def __getattr__(self, name):return getattr(self._items, name)# Added special methods to support certain list operationsdef __len__(self):return len(self._items)def __getitem__(self, index):return self._items[index]def __setitem__(self, index, value):self._items[index] = valuedef __delitem__(self, index):del self._items[index]
