書(shū)接上回,不管是實(shí)例還是類(lèi),都用__dict__
來(lái)存儲(chǔ)屬性和方法,可以籠統(tǒng)地把屬性和方法稱(chēng)為成員或者特性,用一句籠統(tǒng)的話(huà)說(shuō),就是__dict__
存儲(chǔ)對(duì)象成員。但,有時(shí)候訪(fǎng)問(wèn)的對(duì)象成員沒(méi)有存在其中,就是這樣:
>>> class A(object):
... pass
...
>>> a = A()
>>> a.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'
x
不是實(shí)例的成員,用a.x
訪(fǎng)問(wèn),就出錯(cuò)了,并且錯(cuò)誤提示中報(bào)告了原因:“'A' object has no attribute 'x'”
在很多情況下,這種報(bào)錯(cuò)是足夠的了。但是,在某種我現(xiàn)在還說(shuō)不出的情況下,你或許不希望這樣報(bào)錯(cuò),或許希望能夠有某種別的提示、操作等。也就是我們更希望能在成員不存在的時(shí)候有所作為,不是等著報(bào)錯(cuò)。
要處理類(lèi)似的問(wèn)題,就要用到本節(jié)中的知識(shí)了。
__getattr__
、__setattr__
和其它類(lèi)似方法還是用上面的例子,如果訪(fǎng)問(wèn)a.x
,它不存在,那么就要轉(zhuǎn)向到某個(gè)操作。我們把這種情況稱(chēng)之為“攔截”。就好像“尋隱者不遇”,卻被童子“遙指杏花村”,將你“攔截”了。在python中,有一些方法就具有這種“攔截”能力。
__setattr__(self,name,value)
:如果要給name賦值,就調(diào)用這個(gè)方法。__getattr__(self,name)
:如果name被訪(fǎng)問(wèn),同時(shí)它不存在的時(shí)候,此方法被調(diào)用。__getattribute__(self,name)
:當(dāng)name被訪(fǎng)問(wèn)時(shí)自動(dòng)被調(diào)用(注意:這個(gè)僅能用于新式類(lèi)),無(wú)論name是否存在,都要被調(diào)用。__delattr__(self,name)
:如果要?jiǎng)h除name,這個(gè)方法就被調(diào)用。如果一時(shí)沒(méi)有理解,不要緊,是正常的。需要用例子說(shuō)明。
>>> class A(object):
... def __getattr__(self, name):
... print "You use getattr"
... def __setattr__(self, name, value):
... print "You use setattr"
... self.__dict__[name] = value
...
類(lèi)A是新式類(lèi),除了兩個(gè)方法,沒(méi)有別的屬性。
>>> a = A()
>>> a.x
You use getattr
a.x
,按照本節(jié)開(kāi)頭的例子,是要報(bào)錯(cuò)的。但是,由于在這里使用了__getattr__(self, name)
方法,當(dāng)發(fā)現(xiàn)x
不存在于對(duì)象的__dict__
中的時(shí)候,就調(diào)用了__getattr__
,即所謂“攔截成員”。
>>> a.x = 7
You use setattr
給對(duì)象的屬性賦值時(shí)候,調(diào)用了__setattr__(self, name, value)
方法,這個(gè)方法中有一句self.__dict__[name] = value
,通過(guò)這個(gè)語(yǔ)句,就將屬性和數(shù)據(jù)保存到了對(duì)象的__dict__
中,如果在調(diào)用這個(gè)屬性:
>>> a.x
7
它已經(jīng)存在于對(duì)象的__dict__
之中。
在上面的類(lèi)中,當(dāng)然可以使用__getattribute__(self, name)
,因?yàn)樗切率筋?lèi)。并且,只要訪(fǎng)問(wèn)屬性就會(huì)調(diào)用它。例如:
>>> class B(object):
... def __getattribute__(self, name):
... print "you are useing getattribute"
... return object.__getattribute__(self, name)
...
為了與前面的類(lèi)區(qū)分,新命名一個(gè)類(lèi)名字。需要提醒注意,在這里返回的內(nèi)容用的是return object.__getattribute__(self, name)
,而沒(méi)有使用return self.__dict__[name]
像是。因?yàn)槿绻眠@樣的方式,就是訪(fǎng)問(wèn)self.__dict__
,只要訪(fǎng)問(wèn)這個(gè)屬性,就要調(diào)用`getattribute``,這樣就導(dǎo)致了無(wú)線(xiàn)遞歸下去(死循環(huán))。要避免之。
>>> b = B()
>>> b.y
you are useing getattribute
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getattribute__
AttributeError: 'B' object has no attribute 'y'
>>> b.two
you are useing getattribute
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getattribute__
AttributeError: 'B' object has no attribute 'two'
訪(fǎng)問(wèn)不存在的成員,可以看到,已經(jīng)被__getattribute__
攔截了,雖然最后還是要報(bào)錯(cuò)的。
>>> b.y = 8
>>> b.y
you are useing getattribute
8
當(dāng)給其賦值后,意味著已經(jīng)在__dict__
里面了,再調(diào)用,依然被攔截,但是由于已經(jīng)在__dict__
內(nèi),會(huì)把結(jié)果返回。
當(dāng)你看到這里,是不是覺(jué)得上面的方法有點(diǎn)魔力呢?不錯(cuò)。但是,它有什么具體應(yīng)用呢?看下面的例子,能給你帶來(lái)啟發(fā)。
#!/usr/bin/env python
# coding=utf-8
"""
study __getattr__ and __setattr__
"""
class Rectangle(object):
"""
the width and length of Rectangle
"""
def __init__(self):
self.width = 0
self.length = 0
def setSize(self, size):
self.width, self.length = size
def getSize(self):
return self.width, self.length
if __name__ == "__main__":
r = Rectangle()
r.width = 3
r.length = 4
print r.getSize()
r.setSize( (30, 40) )
print r.width
print r.length
上面代碼來(lái)自《Beginning Python:From Novice to Professional,Second Edittion》(by Magnus Lie Hetland),根據(jù)本教程的需要,稍作修改。
$ python 21301.py
(3, 4)
30
40
這段代碼已經(jīng)可以正確運(yùn)行了。但是,作為一個(gè)精益求精的程序員??傆X(jué)得那種調(diào)用方式還有可以改進(jìn)的空間。比如,要給長(zhǎng)寬賦值的時(shí)候,必須賦予一個(gè)元組,里面包含長(zhǎng)和寬。這個(gè)能不能改進(jìn)一下呢?
#!/usr/bin/env python
# coding=utf-8
"""
study __getattr__ and __setattr__
"""
class Rectangle(object):
"""
the width and length of Rectangle
"""
def __init__(self):
self.width = 0
self.length = 0
def setSize(self, size):
self.width, self.length = size
def getSize(self):
return self.width, self.length
size = property(getSize, setSize)
if __name__ == "__main__":
r = Rectangle()
r.width = 3
r.length = 4
print r.size
r.size = 30, 40
print r.width
print r.length
以上代碼的運(yùn)行結(jié)果同上。但是,因?yàn)榧恿艘痪?code>size = property(getSize, setSize),使得調(diào)用方法是不是更優(yōu)雅了呢?原來(lái)用r.getSize()
,現(xiàn)在使用r.size
,就好像調(diào)用一個(gè)屬性一樣。難道你不覺(jué)得眼熟嗎?在《多態(tài)和封裝》中已經(jīng)用到過(guò)property函數(shù)了,雖然寫(xiě)法略有差別,但是作用一樣。
本來(lái),這樣就已經(jīng)足夠了。但是,因?yàn)楸竟?jié)中出來(lái)了特殊方法,所以,一定要用這些特殊方法從新演繹一下這段程序。雖然重新演繹的不一定比原來(lái)的好,主要目的是演示本節(jié)的特殊方法應(yīng)用。
#!/usr/bin/env python
# coding=utf-8
class NewRectangle(object):
def __init__(self):
self.width = 0
self.length = 0
def __setattr__(self, name, value):
if name == "size":
self.width, self.length = value
else:
self.__dict__[name] = value
def __getattr__(self, name):
if name == "size":
return self.width, self.length
else:
raise AttributeError
if __name__ == "__main__":
r = NewRectangle()
r.width = 3
r.length = 4
print r.size
r.size = 30, 40
print r.width
print r.length
除了類(lèi)的樣式變化之外,調(diào)用樣式?jīng)]有變。結(jié)果是一樣的。
這就算了解了一些這些屬性了吧。但是,有一篇文章是要必須推薦給讀者閱讀的:Python Attributes and Methods,讀了這篇文章,對(duì)python的對(duì)象屬性和方法會(huì)有更深入的理解。
通過(guò)實(shí)例獲取其屬性(也有說(shuō)特性的,名詞變化了,但是本質(zhì)都是屬性和方法),如果在__dict__
中有相應(yīng)的屬性,就直接返回其結(jié)果;如果沒(méi)有,會(huì)到類(lèi)屬性中找。比如:
#!/usr/bin/env python
# coding=utf-8
class A(object):
author = "qiwsir"
def __getattr__(self, name):
if name != "author":
return "from starter to master."
if __name__ == "__main__":
a = A()
print a.author
print a.lang
運(yùn)行程序:
$ python 21302.py
qiwsir
from starter to master.
當(dāng)a = A()
后,并沒(méi)有為實(shí)例建立任何屬性,或者說(shuō)實(shí)例的__dict__
是空的,這在上節(jié)中已經(jīng)探討過(guò)了。但是如果要查看a.author
,因?yàn)閷?shí)例的屬性中沒(méi)有,所以就去類(lèi)屬性中找,發(fā)現(xiàn)果然有,于是返回其值"qiwsir"
。但是,在找a.lang
的時(shí)候,不僅實(shí)例屬性中沒(méi)有,類(lèi)屬性中也沒(méi)有,于是就調(diào)用了__getattr__()
方法。在上面的類(lèi)中,有這個(gè)方法,如果沒(méi)有__getattr__()
方法呢?如果沒(méi)有定義這個(gè)方法,就會(huì)引發(fā)AttributeError,這在前面已經(jīng)看到了。
這就是通過(guò)實(shí)例查找特性的順序。
更多建議: