探究更多的類屬性,在一些初學(xué)者的教程中,一般很少見。我之所以要在這里也將這部分奉獻(xiàn)出來,就是因?yàn)楸窘坛淌恰癋rom Beginner to Master”。當(dāng)然,不是學(xué)習(xí)了類的更多屬性就能達(dá)到Master水平,但是這是通往Master的一步,雖然在初級(jí)應(yīng)用中,本節(jié)乃至于后面關(guān)于類的屬性用的不很多,但是,這一步邁出去,你就會(huì)在實(shí)踐中有一個(gè)印象,以后需要用到了,知道有這一步,會(huì)對(duì)項(xiàng)目有幫助的。俗話說“藝不壓身”。
__dict__
前面已經(jīng)學(xué)習(xí)過有關(guān)類屬性和實(shí)例屬性的內(nèi)容,并且做了區(qū)分,如果忘記了可以回頭參閱《類(3)》中的“類屬性和實(shí)例屬性”部分。有一個(gè)結(jié)論,是一定要熟悉的,那就是可以通過object.attribute
的方式訪問對(duì)象的屬性。
如果接著那部分內(nèi)容,讀者是否思考過一個(gè)問題:類或者實(shí)例屬性,在python中是怎么存儲(chǔ)的?或者為什么修改或者增加、刪除屬性,我們能不能控制這些屬性?
>>> class A(object):
... pass
...
>>> a = A()
>>> dir(a)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
用dir()
來查看一下,發(fā)現(xiàn)不管是類還是實(shí)例,都有很多屬性,這在前面已經(jīng)反復(fù)出現(xiàn),有點(diǎn)見怪不怪了。不過,這里我們要看一個(gè)屬性:__dict__
,因?yàn)樗且粋€(gè)保存秘密的東西:對(duì)象的屬性。
>>> class Spring(object):
... season = "the spring of class"
...
>>> Spring.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Spring' objects>,
'season': 'the spring of class',
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'Spring' objects>,
'__doc__': None})
為了便于觀察,我將上面的顯示結(jié)果進(jìn)行了換行,每個(gè)鍵值對(duì)一行。
對(duì)于類Spring的__dict__
屬性,可以發(fā)現(xiàn),有一個(gè)鍵'season'
,這就是這個(gè)類的屬性;其值就是類屬性的數(shù)據(jù)。
>>> Spring.__dict__['season']
'the spring of class'
>>> Spring.season
'the spring of class'
用這兩種方式都能得到類屬性的值?;蛘哒fSpring.__dict__['season']
就是訪問類屬性。下面將這個(gè)類實(shí)例化,再看看它的實(shí)例屬性:
>>> s = Spring()
>>> s.__dict__
{}
實(shí)例屬性的__dict__
是空的。有點(diǎn)奇怪?不奇怪,接著看:
>>> s.season
'the spring of class'
這個(gè)其實(shí)是指向了類屬性中的Spring.season
,至此,我們其實(shí)還沒有建立任何實(shí)例屬性呢。下面就建立一個(gè)實(shí)例屬性:
>>> s.season = "the spring of instance"
>>> s.__dict__
{'season': 'the spring of instance'}
這樣,實(shí)例屬性里面就不空了。這時(shí)候建立的實(shí)例屬性和上面的那個(gè)s.season
只不過重名,并且把它“遮蓋”了。這句好是不是熟悉?因?yàn)樵谥v述“實(shí)例屬性”和“類屬性”的時(shí)候就提到了?,F(xiàn)在讀者肯定理解更深入了。
>>> s.__dict__['season']
'the spring of instance'
>>> s.season
'the spring of instance'
此時(shí),那個(gè)類屬性如何?我們看看:
>>> Spring.__dict__['season']
'the spring of class'
>>> Spring.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Spring' objects>, 'season': 'the spring of class', '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__doc__': None})
>>> Spring.season
'the spring of class'
Spring的類屬性沒有受到實(shí)例屬性的影響。
按照前面的講述類屬性和實(shí)例熟悉的操作,如果這時(shí)候?qū)⑶懊娴膶?shí)例屬性刪除,會(huì)不會(huì)回到實(shí)例屬性s.__dict__
為空呢?
>>> del s.season
>>> s.__dict__
{}
>>> s.season
'the spring of class'
果然打回原形。
當(dāng)然,你可以定義其它名稱的實(shí)例屬性,它一樣被存儲(chǔ)到__dict__
屬性里面:
>>> s.lang = "python"
>>> s.__dict__
{'lang': 'python'}
>>> s.__dict__['lang']
'python'
誠然,這樣做僅僅是更改了實(shí)例的__dict__
內(nèi)容,對(duì)Spring.__dict__
無任何影響,也就是說通過Spring.lang
或者Spring.__dict__['lang']
是得不到上述結(jié)果的。
>>> Spring.lang
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Spring' has no attribute 'lang'
>>> Spring.__dict__['lang']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'lang'
那么,如果這樣操作,會(huì)怎樣呢?
>>> Spring.flower = "peach"
>>> Spring.__dict__
dict_proxy({'__module__': '__main__',
'flower': 'peach',
'season': 'the spring of class',
'__dict__': <attribute '__dict__' of 'Spring' objects>, '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__doc__': None})
>>> Spring.__dict__['flower']
'peach'
在類的__dict__
被更改了,類屬性中增加了一個(gè)'flower'屬性。但是,實(shí)例的__dict__
中如何?
>>> s.__dict__
{'lang': 'python'}
沒有被修改。我也是這么想的,哈哈。你此前這這么覺得嗎?然而,還能這樣:
>>> s.flower
'peach'
這個(gè)讀者是否能解釋?其實(shí)又回到了前面第一個(gè)出現(xiàn)s.season
上面了。
通過上面探討,是不是基本理解了實(shí)例和類的__dict__
,并且也看到了屬性的變化特點(diǎn)。特別是,這些屬性都是可以動(dòng)態(tài)變化的,就是你可以隨時(shí)修改和增刪。
屬性如此,方法呢?下面就看看方法(類中的函數(shù))。
>>> class Spring(object):
... def tree(self, x):
... self.x = x
... return self.x
...
>>> Spring.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Spring' objects>,
'__weakref__': <attribute '__weakref__' of 'Spring' objects>,
'__module__': '__main__',
'tree': <function tree at 0xb748fdf4>,
'__doc__': None})
>>> Spring.__dict__['tree']
<function tree at 0xb748fdf4>
結(jié)果跟前面討論屬性差不多,方法tree
也在__dict__
里面呢。
>>> t = Spring()
>>> t.__dict__
{}
又跟前面一樣。雖然建立了實(shí)例,但是在實(shí)例的__dict__
中沒有方法。接下來,執(zhí)行:
>>> t.tree("xiangzhangshu")
'xiangzhangshu'
在類(3)中有一部分內(nèi)容闡述“數(shù)據(jù)流轉(zhuǎn)”,其中有一張圖,其中非常明確顯示出,當(dāng)用上面方式執(zhí)行方法的時(shí)候,實(shí)例t
與self
建立了對(duì)應(yīng)關(guān)系,兩者是一個(gè)外一個(gè)內(nèi)。在方法中self.x = x
,將x的值給了self.x,也就是實(shí)例應(yīng)該擁有了這么一個(gè)屬性。
>>> t.__dict__
{'x': 'xiangzhangshu'}
果然如此。這也印證了實(shí)例t
和self
的關(guān)系,即實(shí)例方法(t.tree('xiangzhangshu')
)的第一個(gè)參數(shù)(self,但沒有寫出來)綁定實(shí)例t,透過self.x來設(shè)定值,即給t.__dict__
添加屬性值。
換一個(gè)角度:
>>> class Spring(object):
... def tree(self, x):
... return x
...
這回方法中沒有將x賦值給self的屬性,而是直接return,結(jié)果是:
>>> s = Spring()
>>> s.tree("liushu")
'liushu'
>>> s.__dict__
{}
是不是理解更深入了?
現(xiàn)在需要對(duì)python中一個(gè)觀點(diǎn):“一切皆對(duì)象”,再深入領(lǐng)悟。以上不管是類還是的實(shí)例的屬性和方法,都是符合object.attribute
格式,并且屬性類似。
當(dāng)你看到這里的時(shí)候,要么明白了類和實(shí)例的__dict__
的特點(diǎn),要么就糊涂了。糊涂也不要緊,再將上面的重復(fù)一遍,特別是自己要敲一敲有關(guān)代碼。(建議一個(gè)最好的方法:用兩個(gè)顯示器,一個(gè)顯示器看本教程,另外一個(gè)顯示器敲代碼。事半功倍的效果。)
需要說明,我們對(duì)__dict__
的探討還留有一個(gè)尾巴:屬性搜索路徑。這個(gè)留在后面講述。
不管是類還是實(shí)例,其屬性都能隨意增加。這點(diǎn)在有時(shí)候不是一件好事情,或許在某些時(shí)候你不希望別人增加屬性。有辦法嗎?當(dāng)然有,請(qǐng)繼續(xù)學(xué)習(xí)。
__slots__
首先聲明,__slots__
能夠限制屬性的定義,但是這不是它存在終極目標(biāo),它存在的終極目標(biāo)更應(yīng)該是一個(gè)在編程中非常重要的方面:優(yōu)化內(nèi)存使用。
>>> class Spring(object):
... __slots__ = ("tree", "flower")
...
>>> dir(Spring)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'flower', 'tree']
仔細(xì)看看dir()
的結(jié)果,還有__dict__
屬性嗎?沒有了,的確沒有了。也就是說__slots__
把__dict__
擠出去了,它進(jìn)入了類的屬性。
>>> Spring.__slots__
('tree', 'flower')
這里可以看出,類Spring有且僅有兩個(gè)屬性。
>>> t = Spring()
>>> t.__slots__
('tree', 'flower')
實(shí)例化之后,實(shí)例的__slots__
與類的完全一樣,這跟前面的__dict__
大不一樣了。
>>> Spring.tree = "liushu"
通過類,先賦予一個(gè)屬性值。然后,檢驗(yàn)一下實(shí)例能否修改這個(gè)屬性:
>>> t.tree = "guangyulan"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Spring' object attribute 'tree' is read-only
看來,我們的意圖不能達(dá)成,報(bào)錯(cuò)信息中顯示,tree
這個(gè)屬性是只讀的,不能修改了。
>>> t.tree
'liushu'
因?yàn)榍懊嬉呀?jīng)通過類給這個(gè)屬性賦值了。不能用實(shí)例屬性來修改。只能:
>>> Spring.tree = "guangyulan"
>>> t.tree
'guangyulan'
用類屬性修改。但是對(duì)于沒有用類屬性賦值的,可以通過實(shí)例屬性:
>>> t.flower = "haitanghua"
>>> t.flower
'haitanghua'
但此時(shí):
>>> Spring.flower
<member 'flower' of 'Spring' objects>
實(shí)例屬性的值并沒有傳回到類屬性,你也可以理解為新建立了一個(gè)同名的實(shí)例屬性。如果再給類屬性賦值,那么就會(huì)這樣了:
>>> Spring.flower = "ziteng"
>>> t.flower
'ziteng'
當(dāng)然,此時(shí)在給t.flower
重新賦值,就會(huì)爆出跟前面一樣的錯(cuò)誤了。
>>> t.water = "green"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Spring' object has no attribute 'water'
這里試圖給實(shí)例新增一個(gè)屬性,也失敗了。
看來__slots__
已經(jīng)把實(shí)例屬性牢牢地管控了起來,但更本質(zhì)是的是優(yōu)化了內(nèi)存。誠然,這種優(yōu)化會(huì)在大量的實(shí)例時(shí)候顯出效果。
更多建議: