前言
《編寫高質(zhì)量python代碼的59個有效方法》這本書分類逐條地介紹了編寫python代碼的有效思路和方法,對理解python和提高編程效率有一定的幫助。本筆記簡要整理其中的重要方法。
承接上文http://www.itdecent.cn/p/15a6050220e6
http://www.itdecent.cn/p/1f6a2b3b502e
本篇介紹關(guān)于元類及屬性的編程方法
4.元類(MetaClass)及屬性
Python中的元類概念是一種比較模糊的概念,簡單來講就是可以把python中的class語句轉(zhuǎn)譯為元類,令其在每次定義具體的類時都提供獨特的行為。
Python還具有一個奇妙的特性:可以動態(tài)地定義對屬性的訪問操作,這種動態(tài)屬性也有可能帶來令人意外的副作用。
用純屬性取代get和set方法
# C++等其他語言常用寫法
# 在類中明確實現(xiàn)getter和setter
class OldResistor(object):
def __init__(self,ohms):
self._ohms=ohms
def get_ohms(self):
return self._ohms
def set_ohms(self,ohms):
self._ohms=ohms
r0=OldResistor(5)
print(r0.get_ohms())
r0.set_ohms(10)
print(r0.get_ohms())
r0.set_ohms(r0.get_ohms()+5)
這種自定義獲取和設(shè)置的操作,在其他語言中很常見,但在python中不需要手工實現(xiàn),直接從public屬性開始:
class Resistor(object):
def __init__(self,ohms):
self.ohms=ohms
self.vol=0
self.current=0
r1=Resistor(5)
print(r1.ohms) # 5
r1.ohms=10
print(r1.ohms) # 10
此外,還可以通過@property和setter來實現(xiàn)一些特殊操作,如下所示,在給成員變量_vol賦值的同時,還進行了其他變量的修改。
class VolResistance(Resistor):
def __init__(self,ohms):
super().__init__(ohms)
self._vol=0
@property
def vol(self):
return self._vol
@vol.setter
def vol(self,vol):
self._vol=vol
self.current=self._vol/self.ohms
r2=VolResistance(5)
print(r2.current) # 0
r2.vol=10
print(r2.current) #2.0
@property和setter合作實現(xiàn)對類成員屬性的修改,@property相當于getter操作,獲取成員信息,而同時會默認創(chuàng)建相應的xx.setter裝飾器,可以動態(tài)地對相應的成員屬性進行修改。
用描述符來改寫需要復用的@property方法
內(nèi)置的@property修飾器存在不便于復用的問題,受它修飾的方法無法為同一類中的其他屬性所復用。
Python提供了描述符(descriptor)來做,對訪問操作進行一定轉(zhuǎn)譯:描述符類提供get和set方法
class Grade(object):
def __get__(*args,**kwargs):
pass
def __set__(*args,**kwargs):
pass
class Exam(object):
math_grade=Grade()
writing_grade=Grade()
science_grade=Grade()

在具體使用中,我們可以將每個實例對應的值記錄到Grade描述符類中,使用字典保存每個實例的狀態(tài)
class Grade(object):
def __init__(self):
self._values={}
def __get__(self,instance,instance_type):
print(self._values)
if instance is None:
return self
return self._values.get(instance,0)
def __set__(self,instance,value):
self._values[instance]=value
class Exam(object):
math_grade=Grade()
writing_grade=Grade()
science_grade=Grade()
exam=Exam()
exam.science_grade=40
print(exam.science_grade)
#{<__main__.Exam object at 0x7f1b6281ab90>: 40}
#40
#
這種寫法存在一個嚴重的問題:泄露內(nèi)存;在程序的生命期內(nèi),對于傳給set的每個Exam實例,_values字典都會保存指向該實例的一份引用,導致該實例的引用計數(shù)無法將為0,使得內(nèi)存無法被回收。 可以使用內(nèi)置的weakref模塊,使用WeakKeyDictionary的特殊字典,替代普通的字典。
from weakref import WeakKeyDictionary
class Grade(object):
def __init__(self):
self._values=WeakKeyDictionary()
def __get__(self,instance,instance_type):
print(self._values)
if instance is None:
return self
return self._values.get(instance,0)
def __set__(self,instance,value):
self._values[instance]=value
用_getattr_ /_getattribute_ /_setattr_實現(xiàn)需求
Python提供了一些掛鉤(Hook),輔助通用代碼的編寫,將多個系統(tǒng)粘合起來。
例如:把數(shù)據(jù)庫的行表示為Python對象,往往操作時優(yōu)勢我們不清楚行的結(jié)構(gòu),需要些通用的代碼結(jié)構(gòu)。
class DB(object):
def __init__(self):
self.exists=5
def __getattr__(self,name):
value='Value for %s'%name
setattr(self,name,value)
return value
data=DB()
print(data.__dict__) ## {'exists': 5}
print(data.foo)
print(data.__dict__) ## {'exists': 5, 'foo': 'Value for foo'}
在訪問data缺少的foo屬性時,會調(diào)用定義的getattr方法,從而修改實例的dict字典;這種行為適合實現(xiàn)無結(jié)構(gòu)數(shù)據(jù)的按需訪問,初次執(zhí)行getattr把相關(guān)的屬性加載進來。
然而有些情況下,我們需要動態(tài)地訪問對象屬性,使用_getattr_會導致屬性加載后一直存在屬性字典中;因此可以使用另一個方法:_getattribute_,每次訪問對象屬性時都會觸發(fā)該方法,即使屬性已經(jīng)存在與屬性字典中。
此外,可以用內(nèi)置的hashattr函數(shù)判斷對象是否已經(jīng)擁有了相關(guān)的屬性,并用內(nèi)置的getattr函數(shù)來獲取屬性值。
在利用獲取方法得到具體屬性,可以通過_setattr_進行屬性的賦值操作。

此外值得注意的一點是,要避免通過_getattribute_和_setattr_方法中訪問實例屬性,容易造成無限遞歸。

這個時候要采用super()._get__attribute_方法,從實例的屬性字典里面直接獲取_data屬性值,以避免無限遞歸。
