8.15 屬性的代理訪問

2018-02-24 15:26 更新

問題

你想將某個(gè)實(shí)例的屬性訪問代理到內(nèi)部另一個(gè)實(shí)例中去,目的可能是作為繼承的一個(gè)替代方法或者實(shí)現(xiàn)代理模式。

解決方案

簡單來說,代理是一種編程模式,它將某個(gè)操作轉(zhuǎn)移給另外一個(gè)對(duì)象來實(shí)現(xiàn)。最簡單的形式可能是像下面這樣:

class A:
    def spam(self, x):
        pass

    def foo(self):
        pass

class B1:
    """簡單的代理"""

    def __init__(self):
        self._a = A()

    def spam(self, x):
        # Delegate to the internal self._a instance
        return self._a.spam(x)

    def foo(self):
        # Delegate to the internal self._a instance
        return self._a.foo()

    def bar(self):
        pass

如果僅僅就兩個(gè)方法需要代理,那么像這樣寫就足夠了。但是,如果有大量的方法需要代理,那么使用 __getattr__() 方法或許或更好些:

class B2:
    """使用__getattr__的代理,代理方法比較多時(shí)候"""

    def __init__(self):
        self._a = A()

    def bar(self):
        pass

    # Expose all of the methods defined on class A
    def __getattr__(self, name):
        """這個(gè)方法在訪問的attribute不存在的時(shí)候被調(diào)用
        the __getattr__() method is actually a fallback method
        that only gets called when an attribute is not found"""
        return getattr(self._a, name)

__getattr__ 方法是在訪問attribute不存在的時(shí)候被調(diào)用,使用演示:

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

另外一個(gè)代理例子是實(shí)現(xiàn)代理模式,例如:

# A proxy class that wraps around another object, but
# exposes its public attributes
class Proxy:
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute lookup to internal obj
    def __getattr__(self, name):
        print('getattr:', name)
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            print('setattr:', name, value)
            setattr(self._obj, name, value)

    # Delegate attribute deletion
    def __delattr__(self, name):
        if name.startswith('_'):
            super().__delattr__(name)
        else:
            print('delattr:', name)
            delattr(self._obj, name)

使用這個(gè)代理類時(shí),你只需要用它來包裝下其他類即可:

class Spam:
    def __init__(self, x):
        self.x = x

    def bar(self, y):
        print('Spam.bar:', self.x, y)

# Create an instance
s = Spam(2)
# Create a proxy around it
p = Proxy(s)
# Access the proxy
print(p.x)  # Outputs 2
p.bar(3)  # Outputs "Spam.bar: 2 3"
p.x = 37  # Changes s.x to 37

通過自定義屬性訪問方法,你可以用不同方式自定義代理類行為(比如加入日志功能、只讀訪問等)。

討論

代理類有時(shí)候可以作為繼承的替代方案。例如,一個(gè)簡單的繼承如下:

class A:
    def spam(self, x):
        print('A.spam', x)
    def foo(self):
        print('A.foo')

class B(A):
    def spam(self, x):
        print('B.spam')
        super().spam(x)
    def bar(self):
        print('B.bar')

使用代理的話,就是下面這樣:

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)

當(dāng)實(shí)現(xiàn)代理模式時(shí),還有些細(xì)節(jié)需要注意。首先,__getattr__() 實(shí)際是一個(gè)后備方法,只有在屬性不存在時(shí)才會(huì)調(diào)用。因此,如果代理類實(shí)例本身有這個(gè)屬性的話,那么不會(huì)觸發(fā)這個(gè)方法的。另外,__setattr__()__delattr__() 需要額外的魔法來區(qū)分代理實(shí)例和被代理實(shí)例 _obj 的屬性。一個(gè)通常的約定是只代理那些不以下劃線 _ 開頭的屬性(代理類只暴露被代理類的公共屬性)。

還有一點(diǎn)需要注意的是,__getattr__() 對(duì)于大部分以雙下劃線(__)開始和結(jié)尾的屬性并不適用。比如,考慮如下的類:

class ListLike:
    """__getattr__對(duì)于雙下劃線開始和結(jié)尾的方法是不能用的,需要一個(gè)個(gè)去重定義"""

    def __init__(self):
        self._items = []

    def __getattr__(self, name):
        return getattr(self._items, name)

如果是創(chuàng)建一個(gè)ListLike對(duì)象,會(huì)發(fā)現(xiàn)它支持普通的列表方法,如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
>>>

為了讓它支持這些方法,你必須手動(dòng)的實(shí)現(xiàn)這些方法代理:

class ListLike:
    """__getattr__對(duì)于雙下劃線開始和結(jié)尾的方法是不能用的,需要一個(gè)個(gè)去重定義"""

    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]

11.8小節(jié)還有一個(gè)在遠(yuǎn)程方法調(diào)用環(huán)境中使用代理的例子。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)