你想将某个实例的属性访问代理到内部另一个实例中去,目的可能是作为继承的一个替代方法或者实现代理模式。
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):
pass
class 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 bar
This is A spam is: 42
This is A foo
1. 代理模式
class Proxy:
def __init__(self, obj):
self._obj = obj
def __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 = x
def bar(self, y):
print('Spam.bar', self.x, y)
s = Spam(2)
p = Proxy(s)
print(p.x)
p.bar(3)
p.x = 37
getattr x
2
getattr bar
Spam.bar 2 3
setter: 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 3
A.spam 3
B.bar
A.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 operations
def __len__(self):
return len(self._items)
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, value):
self._items[index] = value
def __delitem__(self, index):
del self._items[index]