什么是描述符
描述符是Python新式類的關(guān)鍵點(diǎn)之一墓贿,它為對(duì)象屬性提供強(qiáng)大的API,你可以認(rèn)為描述符是表示對(duì)象屬性的一個(gè)代理炼列。當(dāng)需要屬性時(shí),可根據(jù)你遇到的情況音比,通過(guò)描述符進(jìn)行訪問(wèn)他(摘自Python核心編程)俭尖。
實(shí)例解析
-
使用類方法創(chuàng)建描述符
就是將某種特殊類型的類的實(shí)例指派給另一個(gè)類的屬性(注意:這里是類屬性,而不是對(duì)象屬性)。而這種特殊類型的類就是實(shí)現(xiàn)了
__get__
稽犁,__set__
,__delete__
的新式類(即繼承object)焰望。
__get__(self, object, type) # 用于得到一個(gè)屬性的值
__set__(self, obj, val) # 用于為一個(gè)屬性賦值
__delete__(self, obj) # 刪除某個(gè)屬性時(shí)被調(diào)用,但很少用到
其中只實(shí)現(xiàn)了__set__()
方法的被當(dāng)做方法描述符已亥,或者是非數(shù)據(jù)描述符熊赖。那些同時(shí)實(shí)現(xiàn)了__set__()
和__get__()
方法的類被稱作數(shù)據(jù)描述符。
首先定義一個(gè)數(shù)據(jù)描述符類
# coding=utf-8
class Descriptor(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, owner):
print "訪問(wèn)屬性"
return self.value
def __set__(self, instance, value):
print "設(shè)置屬性值"
self.value = value
再來(lái)定義一個(gè)調(diào)用數(shù)據(jù)描述符的類
class Myclass(object):
desc = Descriptor(5)
if __name__ == '__main__':
print Myclass.desc
訪問(wèn)結(jié)果為:
訪問(wèn)屬性
5
發(fā)現(xiàn)訪問(wèn)Myclass的desc屬性時(shí)虑椎,調(diào)用了描述符的__get__()
方法震鹉。這就達(dá)到了描述符的作用(可以改變對(duì)象屬性的訪問(wèn))。
調(diào)用原理:對(duì)于類屬性描述符捆姜,如果解析器發(fā)現(xiàn)屬性x是一個(gè)描述符的話传趾,在內(nèi)部通過(guò)
type.__getattribute__()
(訪問(wèn)屬性時(shí)無(wú)條件調(diào)用,最先調(diào)用)泥技,它能把Class.x
轉(zhuǎn)換成Class.__dict__[‘x’].__get__(None, Class)
來(lái)訪問(wèn)
上面把描述符定義成了類屬性墨缘,那我們要把他定義成對(duì)象屬性會(huì)有什么樣的異同呢?
# coding=utf-8
class Descriptor(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, owner):
print "訪問(wèn)屬性"
return self.value
def __set__(self, instance, value):
print "設(shè)置屬性值"
self.value = value
class Myclass(object):
def __init__(self):
self.desc = Descriptor(5)
if __name__ == '__main__':
myclass = Myclass()
print myclass.desc
輸出結(jié)果為:
<__main__.Descriptor object at 0x0000000002DFAC18>
并沒(méi)有像我們預(yù)期的那樣調(diào)用__get__()
方法零抬,只是說(shuō)他是Descriptor的一個(gè)對(duì)象镊讼。
這是因?yàn)楫?dāng)訪問(wèn)實(shí)例描述符對(duì)象時(shí),
obj.__getattribute__()
會(huì)將myclass.desc
轉(zhuǎn)換為type(myclass).__dict__['desc'].__get__(myclass, type(myclass))
平夜,即到類屬性中去尋找desc蝶棋,并調(diào)用他的__get__()
方法。而Myclass類中沒(méi)有desc屬性忽妒,所以無(wú)法訪調(diào)用到__get__
方法.
描述符是一個(gè)類屬性玩裙,必須定義在類的層次上, 而不能單純的定義為對(duì)象屬性。
那么當(dāng)定義類屬性描述符對(duì)象和實(shí)例屬性名字相同時(shí)段直,會(huì)有什么樣的效果呢吃溅?
# coding=utf-8
class Descriptor(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, owner):
print "訪問(wèn)屬性"
return self.value
def __set__(self, instance, value):
print "設(shè)置屬性值"
self.value = value
class Myclass(object):
desc = Descriptor(5)
def __init__(self, desc):
self.desc = desc # 與類屬性同名的屬性
if __name__ == '__main__':
myclass = Myclass(3)
print myclass.desc
運(yùn)行結(jié)果如下:
設(shè)置屬性值
訪問(wèn)屬性
3
可以看出初始化時(shí)訪問(wèn)了描述符的__set__()
方法,訪問(wèn)屬性值時(shí)訪問(wèn)了描述符的__get__()
方法鸯檬。這樣為什么又調(diào)用描述符的方法了呢决侈?
為了解釋這個(gè)問(wèn)題,我們要先說(shuō)一下在python
中訪問(wèn)一個(gè)屬性的優(yōu)先級(jí)喧务,如下:
- 類屬性
- 數(shù)據(jù)描述符
- 實(shí)例屬性
- 非數(shù)據(jù)描述符
- 默認(rèn)為getattr()(找不到的情況下)
然后我們打印出上面代碼類和實(shí)例的屬性列表:
if __name__ == '__main__':
myclass = Myclass(3)
print "instance: ", myclass.__dict__
print "Class: ", Myclass.__dict__
結(jié)果如下:
instance: {}
Class: {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>, '__doc__': None, '__init__': <function __init__ at 0x00000000030F9438>, 'desc': <__main__.Descriptor object at 0x00000000030E8B70>}
可以發(fā)現(xiàn)實(shí)例對(duì)象的屬性中并沒(méi)有desc
赖歌,而相反,類屬性中卻有它功茴。這是為什么呢庐冯?
按照上面的屬性訪問(wèn)優(yōu)先級(jí)的理論,數(shù)據(jù)描述符 > 實(shí)例屬性坎穿。當(dāng)python發(fā)現(xiàn)實(shí)例對(duì)象的字典中有與定義的描述符有相同名字的對(duì)象時(shí)展父,描述符優(yōu)先返劲,會(huì)覆蓋掉實(shí)例屬性。python會(huì)改寫(xiě)默認(rèn)的行為栖茉,去調(diào)用描述符的方法來(lái)代替篮绿。
我們來(lái)驗(yàn)證一下上面的理論,優(yōu)先級(jí)實(shí)例屬性 > 非數(shù)據(jù)描述符
衡载。首先我們定義一下非數(shù)據(jù)描述符(只有__get__()
方法)
# coding=utf-8
class Descriptor(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, owner):
print "訪問(wèn)屬性"
return self.value
# def __set__(self, instance, value):
# print "設(shè)置屬性值"
# self.value = value
class Myclass(object):
desc = Descriptor(5)
def __init__(self, desc):
self.desc = desc # 與類屬性同名的屬性
if __name__ == '__main__':
myclass = Myclass(3)
print myclass.desc
print "instance: ", myclass.__dict__
print "Class: ", Myclass.__dict__
運(yùn)行一下:
3
instance: {'desc': 3}
Class: {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>, '__doc__': None, '__init__': <function __init__ at 0x0000000002E393C8>, 'desc': <__main__.Descriptor object at 0x0000000002E28B00>}
可以看出搔耕,這種情況下訪問(wèn)實(shí)例屬性,并沒(méi)有調(diào)用描述符的__get__()
方法痰娱。而是調(diào)用了本身的屬性弃榨。可以看出理論是正確的梨睁。
-
使用屬性類型創(chuàng)建描述符
屬性是一種有用的特殊類型的描述符鲸睛。他們是用來(lái)處理所有對(duì)實(shí)例屬性的訪問(wèn),其工作方法和前面說(shuō)過(guò)的描述符類似坡贺。通過(guò)使用 property()官辈,可以輕松地為任意屬性創(chuàng)建可用的描述符。
property
內(nèi)建函數(shù)有四個(gè)參數(shù):property(fget=None, fset=None, fdel=None, doc=None)
遍坟。
fget:屬性獲取方法
fset:屬性設(shè)置方法
fdel:屬性刪除方法
doc:文檔描述
來(lái)看一下實(shí)現(xiàn):
class PropertyDesc(object):
def __init__(self):
self._name = ''
def fget(self):
print "Getting: %s" % self._name
return self._name
def fset(self, value):
print "Setting: %s" % value
self._name = value
def fdel(self):
print "Deleting: %s" %self._name
del self._name
name = property(fget, fset, fdel, "I'm the property.")
if __name__ == '__main__':
pro = PropertyDesc()
pro.name = "haha"
print pro.name
del pro.name
結(jié)果如下:
Setting: haha
Getting: haha
haha
Deleting: haha
這樣實(shí)現(xiàn)描述符拳亿,雖然簡(jiǎn)單。但屬性多的話就造成代碼臃腫不堪愿伴。
-
使用屬性修飾符創(chuàng)建描述符
class PropertyDesc(object):
def __init__(self):
self._name = ''
@property
def name(self):
print "Getting: %s" % self._name
return self._name
@name.setter
def name(self, value):
print "Setting: %s" % value
self._name = value
@name.deleter
def name(self):
print "Deleting: %s" %self._name
del self._name
if __name__ == '__main__':
pro = PropertyDesc()
pro.name = "haha"
print pro.name
del pro.name
運(yùn)行結(jié)果如下:
Setting: haha
Getting: haha
haha
Deleting: haha
看肺魁,代碼運(yùn)行如初。具體原理就不再贅述隔节《炀可以打印出PropertyDesc
類和實(shí)例pro
的屬性列表進(jìn)行思考。