• 8.15 属性的代理访问
    • 问题
    • 解决方案
    • 讨论

    8.15 属性的代理访问

    问题

    你想将某个实例的属性访问代理到内部另一个实例中去,目的可能是作为继承的一个替代方法或者实现代理模式。

    解决方案

    简单来说,代理是一种编程模式,它将某个操作转移给另外一个对象来实现。最简单的形式可能是像下面这样:

    1. class A:
    2. def spam(self, x):
    3. pass
    4.  
    5. def foo(self):
    6. pass
    7.  
    8.  
    9. class B1:
    10. """简单的代理"""
    11.  
    12. def __init__(self):
    13. self._a = A()
    14.  
    15. def spam(self, x):
    16. # Delegate to the internal self._a instance
    17. return self._a.spam(x)
    18.  
    19. def foo(self):
    20. # Delegate to the internal self._a instance
    21. return self._a.foo()
    22.  
    23. def bar(self):
    24. pass

    如果仅仅就两个方法需要代理,那么像这样写就足够了。但是,如果有大量的方法需要代理,那么使用 getattr() 方法或许或更好些:

    1. class B2:
    2. """使用__getattr__的代理,代理方法比较多时候"""
    3.  
    4. def __init__(self):
    5. self._a = A()
    6.  
    7. def bar(self):
    8. pass
    9.  
    10. # Expose all of the methods defined on class A
    11. def __getattr__(self, name):
    12. """这个方法在访问的attribute不存在的时候被调用
    13. the __getattr__() method is actually a fallback method
    14. that only gets called when an attribute is not found"""
    15. return getattr(self._a, name)

    getattr 方法是在访问attribute不存在的时候被调用,使用演示:

    1. b = B()
    2. b.bar() # Calls B.bar() (exists on B)
    3. b.spam(42) # Calls B.__getattr__('spam') and delegates to A.spam

    另外一个代理例子是实现代理模式,例如:

    1. # A proxy class that wraps around another object, but
    2. # exposes its public attributes
    3. class Proxy:
    4. def __init__(self, obj):
    5. self._obj = obj
    6.  
    7. # Delegate attribute lookup to internal obj
    8. def __getattr__(self, name):
    9. print('getattr:', name)
    10. return getattr(self._obj, name)
    11.  
    12. # Delegate attribute assignment
    13. def __setattr__(self, name, value):
    14. if name.startswith('_'):
    15. super().__setattr__(name, value)
    16. else:
    17. print('setattr:', name, value)
    18. setattr(self._obj, name, value)
    19.  
    20. # Delegate attribute deletion
    21. def __delattr__(self, name):
    22. if name.startswith('_'):
    23. super().__delattr__(name)
    24. else:
    25. print('delattr:', name)
    26. delattr(self._obj, name)

    使用这个代理类时,你只需要用它来包装下其他类即可:

    1. class Spam:
    2. def __init__(self, x):
    3. self.x = x
    4.  
    5. def bar(self, y):
    6. print('Spam.bar:', self.x, y)
    7.  
    8. # Create an instance
    9. s = Spam(2)
    10. # Create a proxy around it
    11. p = Proxy(s)
    12. # Access the proxy
    13. print(p.x) # Outputs 2
    14. p.bar(3) # Outputs "Spam.bar: 2 3"
    15. p.x = 37 # Changes s.x to 37

    通过自定义属性访问方法,你可以用不同方式自定义代理类行为(比如加入日志功能、只读访问等)。

    讨论

    代理类有时候可以作为继承的替代方案。例如,一个简单的继承如下:

    1. class A:
    2. def spam(self, x):
    3. print('A.spam', x)
    4. def foo(self):
    5. print('A.foo')
    6.  
    7. class B(A):
    8. def spam(self, x):
    9. print('B.spam')
    10. super().spam(x)
    11. def bar(self):
    12. print('B.bar')

    使用代理的话,就是下面这样:

    1. class A:
    2. def spam(self, x):
    3. print('A.spam', x)
    4. def foo(self):
    5. print('A.foo')
    6.  
    7. class B:
    8. def __init__(self):
    9. self._a = A()
    10. def spam(self, x):
    11. print('B.spam', x)
    12. self._a.spam(x)
    13. def bar(self):
    14. print('B.bar')
    15. def __getattr__(self, name):
    16. return getattr(self._a, name)

    当实现代理模式时,还有些细节需要注意。首先,getattr() 实际是一个后备方法,只有在属性不存在时才会调用。因此,如果代理类实例本身有这个属性的话,那么不会触发这个方法的。另外,setattr()delattr() 需要额外的魔法来区分代理实例和被代理实例 obj 的属性。一个通常的约定是只代理那些不以下划线 开头的属性(代理类只暴露被代理类的公共属性)。

    还有一点需要注意的是,getattr() 对于大部分以双下划线(__)开始和结尾的属性并不适用。比如,考虑如下的类:

    1. class ListLike:
    2. """__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义"""
    3.  
    4. def __init__(self):
    5. self._items = []
    6.  
    7. def __getattr__(self, name):
    8. return getattr(self._items, name)

    如果是创建一个ListLike对象,会发现它支持普通的列表方法,如append()和insert(),但是却不支持len()、元素查找等。例如:

    1. >>> a = ListLike()
    2. >>> a.append(2)
    3. >>> a.insert(0, 1)
    4. >>> a.sort()
    5. >>> len(a)
    6. Traceback (most recent call last):
    7. File "<stdin>", line 1, in <module>
    8. TypeError: object of type 'ListLike' has no len()
    9. >>> a[0]
    10. Traceback (most recent call last):
    11. File "<stdin>", line 1, in <module>
    12. TypeError: 'ListLike' object does not support indexing
    13. >>>

    为了让它支持这些方法,你必须手动的实现这些方法代理:

    1. class ListLike:
    2. """__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义"""
    3.  
    4. def __init__(self):
    5. self._items = []
    6.  
    7. def __getattr__(self, name):
    8. return getattr(self._items, name)
    9.  
    10. # Added special methods to support certain list operations
    11. def __len__(self):
    12. return len(self._items)
    13.  
    14. def __getitem__(self, index):
    15. return self._items[index]
    16.  
    17. def __setitem__(self, index, value):
    18. self._items[index] = value
    19.  
    20. def __delitem__(self, index):
    21. del self._items[index]

    11.8小节还有一个在远程方法调用环境中使用代理的例子。

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p15_delegating_attribute_access.html