1. 程式人生 > >深入理解python屬性

深入理解python屬性

在java等編譯語言中,我們傾向於將成員變數設為private,通過public的get/set方法來訪問對應的欄位,這樣做的好處在於get/set起到了攔截的作用,在這個點我們可以插入自己的邏輯,也就是常說的AOP(面向切面程式設計),比如日誌、快取、延遲建立等操作。

但是python並不推薦這種方式,主要是python的“約定大於配置“原則決定私有成員變數並不是完全不可訪問,實際上只是變了下名字隱藏而已,所以設為私有沒有意義,正常使用需要對外可讀寫的成員變數一律推薦設為public

注意這裡說的的欄位和屬性的區別,在python語言中類成員欄位也稱為屬性,而在C#等中二者嚴格區分(成員變數通常被稱為欄位,對應的包裝方法或名稱稱為屬性

)。儘管python中不推薦private屬性,但是它也有自己實現屬性AOP的一套方式,效果類似get/set方法而且更為靈活。

@Property修飾器

@Property可以實現get/set方法,如下:

class Person(object):
    def __init__(self):
        self._name = None
        self._eng_name = None

    @property
    def name(self):
        print('---Get name value---')
        return self._name

    @name.setter
    def name(self, name):
        print('---Set name value---')
        self._name = name
        self._eng_name = name+'_eng'


if __name__ == '__main__':
    p = Person()
    p.name = 'wenzhou'
    print("eng_name=", p._eng_name)

這裡:@property實現get方法 ,name_setter實現set方法,和get/set方法效果一致,對應name為新增的動態屬性

注意:

1.python 2.X實現property的必須為新式類(繼承object)

2.成員變數(_name)和動態屬性(name)命名不能一樣,否則會陷入死迴圈

描述符機制

儘管上述@property可以模擬set/get方法,但是只能在當前類和其子類中生效,不同的類之間沒法公用。仔細思考這個問題,對每個屬性的get/set實際上是對屬性本身的一個約束,所以可以將不同的欄位約束抽象到一個類中來描述,這就是描述符。

比如,我們定義如下類:

class ValueRow(object):
    value_1 = MyField()
    value_2 = MyField()

    def __init__(self):
        self.value_3 = 10

這裡的value_1和value_2屬性我們都指定成MyField類例項,在MyField類中實現對應的約束描述協議,如下:

from weakref import WeakKeyDictionary

class MyField(object):
    def __init__(self):
        print 'init MyField'
        self._values = WeakKeyDictionary()

    def __get__(self, instance, owner):
        return self._values.get(instance, None) if instance else None

    def __set__(self, instance, value):
        if not (0<=value<=100):
            raise ValueError("Must between 0 and 100.")

        self._values[instance] = value

實現這裡的__get__和__set__方法的即為描述符,每當對應的欄位取值和設值的時候就會呼叫這裡的get/set方法完成。每個欄位的描述符在這個類中匯入完成後,就會在記憶體中建立一個對應描述符例項,就算有多個類例項也只會公用這一個描述符例項,因此必須按照instance為key來儲存值

另外,注意這裡_values按照Instance為key索引值,持有一份索引,會導致記憶體洩漏,因此改用weakref或者直接使用setattr/getattr來儲存例項相關的資料。

如下呼叫:

if __name__ == '__main__':
    pass

輸出如下:

init MyField
init MyField

可見,MyField在類匯入完成後即建立了一份例項。

 

再如下呼叫:

if __name__ == '__main__':
    v1 = ValueRow()
    v1.value_1 = 90
    print 'value 1=%d ' %v1.value_1

    v2 = ValueRow()
    v2.value_1 = 91
    print 'value 2=%d ' %v2.value_1

    v2.value_1 = 101

輸出如下:

Connected to pydev debugger (build 181.5087.37)
init MyField
init MyField
value 1=90 
value 2=91 
Traceback (most recent call last):...

可以看到兩個ValueRow例項只初始化建立ValueRow一次,v2.value_1=101不滿足0<=value<=100,所以丟擲了異常。

屬性Hook

python最大的特點在於其開放性,對於python類允許我們重寫其屬性獲取和設定方法,如下重寫各個魔術方法如下:

# __getattr__屬性存在則不再呼叫
class LazyDB(object):
    def __getattr__(self, item):
        print('get attr ' + item)

        v = 'new '+item
        # self.item = v #不可行
        setattr(self, item, v)
        return v

# __getattribute__始終呼叫
class ValidateDB(object):
    def __getattribute__(self, item):
        print('get attribute ' + item)

        try:
            return super(ValidateDB).__getattribute__(item) # 從屬性字典獲取,避免迴圈巢狀
        except AttributeError:
            v = 'validate '+item
            setattr(self, item, v)
            return v

# __setattr__始終呼叫
class LoggingDB(object):
    def __setattr__(self, key, value):
        print("the value of %s is %s" % (key, value))
        self.__dict__[key] = value                           # 直接設定屬性字典,避免迴圈巢狀

這裡可以重寫的函式方法為__getattr__、__getattribute__、__setattr__,其中2、3始終呼叫,1只有在屬性不存在時才呼叫。這裡分別實現資料庫延遲建立,資料欄位合法校驗和簡單日誌記錄等AOP功能。

 

演示程式碼下載連結

原創,轉載請註明來自http://blog.csdn.net/wenzhou1219