這是我總結(jié)的Python屬性搜索的加長版筛璧,完整的描述了Python在做屬性訪問時的過程。
了解Python屬性搜索的整個過程房官,有助于更深入地理解Python屬性訪問删壮。
1. 對Python中屬性和方法的看法
理解Python時有一點(diǎn)很重要:一切皆對象搁骑。
為什么呢?
先看個例子:
>>> class Foo(object):
... clsattr = 'this is class attribute'
... def clsmethod(self):
... print 'this is method'
在這個例子中酣衷,定義了class Foo
涯捻。
通常我們稱clsattr
為屬性,clsmethod
為方法墅诡。
這種叫法容易造成一種誤會:認(rèn)為對于類來說屬性和方法是兩種東西壳嚎。
其實(shí),由于在Python中一切皆對象,所以烟馅,拿上例來說说庭,clsattr
是Foo
的一個str
類型對象,而clsmethod
是Foo
的一個function
類型對象郑趁。
也就是對于Foo
來講刊驴,clsattr
和clsmethod
都是對象,除了類型不同寡润,其他的沒有什么不一樣捆憎。
這是一個看法上的細(xì)微區(qū)別,但是對后面理解Python的屬性搜索卻很重要悦穿。
2. Python屬性搜索的精簡版
Python存在類對象和實(shí)例對象之分:
>>> class Foo(object):
... clsattr = 'this is class attribute'
... def clsmethod(self):
... print 'this is method'
...
>>> f = Foo()
在這個例子中Foo
是類對象攻礼,f
是實(shí)例對象。
但是我們知道栗柒,在OOP的理論中礁扮,類是抽象描述,實(shí)例是其具象瞬沦。Python支持OOP太伊,所以,也能通過自己的方式來“模擬并實(shí)現(xiàn)” OOP的要求逛钻。
其中最重要的就是屬性搜索規(guī)則僚焦。
當(dāng)不考慮描述符/描述器的時候,可以將Python屬性搜索精簡如下:
- Python的屬性搜索是按照繼承樹從下到上進(jìn)行的曙痘。
- 繼承樹以類對象為中心芳悲,向上是其基類,向下是其實(shí)例對象边坤。
- 當(dāng)通過實(shí)例.屬性的形式訪問某屬性時名扛,首先查找實(shí)例對象自身是否存在該屬性,如果存在茧痒,那么直接返回肮韧;如果不存在,那么向上查找旺订,直到找到為止弄企,否則返回異常。
還是拿上面的例子來說明:
>>> f.__dict__
{}
>>> Foo.__dict__
dict_proxy({'__module__': '__main__', 'clsattr': 'clsattr', '__dict__': <attribute '__dict__' of 'Foo' objects>, 'foo': <function foo at 0x7f2a86c31cf8>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
Python中對象通過__dict__
內(nèi)置屬性來動態(tài)管理其所有屬性区拳。__dict__
是一個字典拘领,其內(nèi)部為對象所有屬性名與屬性對象的對應(yīng)關(guān)系。
可以看到clsattr
在類對象Foo
中存在樱调,在實(shí)例對象f
中不存在院究。
因此洽瞬,當(dāng)通過f.clsattr
的形式訪問屬性clsattr
的時候,首先业汰,查找實(shí)例對象f
,沒有找到菩颖,所以繼續(xù)向上查找样漆,于是查找類對象Foo
,找到晦闰,返回其值放祟。
按照這個規(guī)則,如果f
中存在clsattr
屬性呻右,則不會去查找Foo
跪妥,即:
>>> f.clsattr = 'this is an instance attr'
>>> f.__dict__
{'clsattr': 'this is an instance attr'}
>>> f.clsattr
'this is an instance attr'
OK,了解了精簡版 的屬性搜索規(guī)則声滥,再來看看加長版 的規(guī)則眉撵。
3. Python屬性搜索的加長版
正如上面提到的,在不考慮描述符/描述器的時候落塑,才可以按照精簡版的規(guī)則來纽疟,那么考慮到描述符的時候呢?
關(guān)于描述符就不詳細(xì)展開了憾赁。簡單的說污朽,描述符就是一個實(shí)現(xiàn)了描述符協(xié)議 的類。
描述符協(xié)議包括__get__
龙考、__set__
蟆肆、__delete__
方法。
其中晦款,只實(shí)現(xiàn)了__get__
方法的描述符稱為non-data descriptor炎功;
而實(shí)現(xiàn)了__get__
和__set__
方法的描述符稱為data descriptor;
當(dāng)把類的一個屬性為描述符這種特殊的對象時柬赐,會發(fā)生一件神奇的事情:
Python在類的__dict__
中找到的對象如果擁有__get__()
方法亡问,不會直接返回這個對象,而是返回其調(diào)用__get__()
方法后的結(jié)果
注意:這里特別要強(qiáng)調(diào)是類的屬性肛宋,不是實(shí)例的屬性州藕。
舉例說明:
>>> class Foo(object):
... clsattr = Descriptor() # 假設(shè)Descriptor是自定義的描述符
... def func(self):
... print 'this is func'
...
那么當(dāng)調(diào)用Foo.__dict__['clsattr']
時,由于clsattr
是描述符酝陈,所以床玻,返回的是Foo.__dict__['clsattr'].__get__(None, Foo)
這個時候,有趣的事情就來了沉帮,當(dāng)調(diào)用函數(shù)func
即Foo.__dict__['func']
時锈死,返回的是Foo.__dict__['func'].__get__(None, Foo)
, 在命令行中這是一個unbound method
所以贫堰,在Python中函數(shù)其實(shí)是一個描述符,只不過待牵,由于描述符必須在通過屬性訪問的情況下才起作用其屏,所以,并沒有察覺缨该。
看到這里偎行,可能你已經(jīng)恍然大悟:其實(shí)方法只是一個特殊的屬性而已。
好了贰拿,理解到這里蛤袒,就可以說說屬性搜索加長版**:
- 當(dāng)通過實(shí)例.屬性訪問一個屬性時,首先膨更,從其類對象開始向上查找妙真,如果存在并且是data-descriptor,那么返回查找結(jié)果荚守;否則珍德,進(jìn)行步驟2
- 在實(shí)例中查找屬性是否存在,如果存在健蕊,則返回其值菱阵,否則,進(jìn)行步驟3
- 向上查找缩功,直到找到晴及,返回其值,否則嫡锌,返回異常
由于描述符只能是類屬性虑稼,所以,可以有以下結(jié)論:
- data descriptor對于同名的實(shí)例屬性(非描述器)有屏蔽作用:
例如:
#!/usr/bin/env python
# -*- conding: utf-8 -*-
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print 'Retrieving', self.name, self.val
return self.val
def __set__(self, obj, val):
print 'Updating', self.name, self.val
class Foo(object):
attr = RevealAccess(10, 'var "x"') #設(shè)置類屬性attr為描述符
def __init__(self):
self.attr = 20 #調(diào)用描述符的__set__方法势木,而不是為實(shí)例設(shè)置屬性
f = Foo()
print f.attr
執(zhí)行結(jié)果如下:
Updating var "x" 10
Retrieving var "x" 10
10
2.non-data descriptor對于同名的屬性蛛倦,會被屏蔽掉。這一點(diǎn)與正常的類屬性相同啦桌。