深入理解python屬性
在java等編譯語言中,我們傾向於將成員變數設為private,通過public的get/set方法來訪問對應的欄位,這樣做的好處在於get/set起到了攔截的作用,在這個點我們可以插入自己的邏輯,也就是常說的AOP(面向切面程式設計),比如日誌、快取、延遲建立等操作。
但是python並不推薦這種方式,主要是python的“約定大於配置“原則決定私有成員變數並不是完全不可訪問,實際上只是變了下名字隱藏而已,所以設為私有沒有意義,正常使用需要對外可讀寫的成員變數一律推薦設為public。
注意這裡說的的欄位和屬性的區別,在python語言中類成員欄位也稱為屬性,而在C#等中二者嚴格區分(成員變數通常被稱為欄位,對應的包裝方法或名稱稱為屬性
@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