本篇主要總結(jié)Python中綁定方法對象(Bound method object)和未綁定方法對象(Unboud method object)的區(qū)別和聯(lián)系。
主要目的是分清楚這兩個極容易混淆的概念凸舵,順便將Python的靜態(tài)方法,類方法及實(shí)例方法加以說明
OK渐苏,下面開始
1. 一個方法引發(fā)的“血案”
類中所定義的函數(shù)稱為方法
舉例:
>>>class Foo(object):
... def foo():
... print 'call foo'
然后令人困惑的地方就來了:
當(dāng)你嘗試使用類名.方法名調(diào)用函數(shù)foo時菇夸,會出現(xiàn)如下錯誤
>>> Foo.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)
看一下報錯信息發(fā)現(xiàn)需要一個Foo
的實(shí)例(instance)來調(diào)用,OK鞠眉,于是調(diào)用如下:
>>> Foo().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes no arguments (1 given)
-.-!!!
估計(jì)脾氣不好的看到做到這里想要罵街了择诈。
因?yàn)閺淖置嫔峡?code>Foo( ).foo( )并沒有傳遞任何參數(shù),而報錯信息卻顯示(1 given)
哗戈。
在Python中一切皆對象荷科,方法是函數(shù)纱注,所以我們來仔細(xì)查看一下函數(shù)對象foo
>>> Foo.foo
<unbound method Foo.foo>
>>> Foo().foo
<bound method Foo.foo of <__main__.Foo object at 0x7ff33b424d50>>
咦~狞贱,發(fā)現(xiàn)一個有趣的現(xiàn)象:
通過類名Foo
獲取類函數(shù)屬性foo
時煮剧,得到的是unbound method object,通過實(shí)例Foo()
獲取類的函數(shù)屬性foo
時佑颇,得到的是bound method object草娜。
在來看看這兩個對象的類型:
>>> type(Foo.foo)
<type 'instancemethod'>
>>> type(Foo().foo)
<type 'instancemethod'>
于是我們產(chǎn)生了更大的疑問:為什么同樣是實(shí)例方法(instancemethod),獲取方式的不同茬贵,會導(dǎo)致獲得不同的對象呢移袍?
2. bound/unbound method是怎么來的
下面讓我們來一層層揭開這個bound/unbound method的面紗。
首先螟左,我們知道,對于類胶背,其屬性是存放在__dict__
字典中喘先,即:
>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, '__module__': '__main__', 'foo': <function foo at 0x7ff33b42a5f0>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
在其中我們看到了'foo': <function foo at 0x7ff33b42a5f0>
窘拯。
然后利用字典查看foo
:
>>> Foo.__dict__['foo']
<function foo at 0x7ff33b42a5f0>
可以看到foo
是一個函數(shù)對象,根據(jù)上一小節(jié)最后一個例子的信息直焙,我們發(fā)現(xiàn),foo是有綁定行為的奔誓。
在Python中使用描述器(有翻譯的鏈接)來表示具有“綁定”行為的對象屬性,使用描述器協(xié)議方法來控制對具有綁定行為屬性的訪問和措,這些描述器協(xié)議方法包括:
__get__()
蜕煌、__set__()
和__delete__()
。
根據(jù)上面這段難以讓人理解的描述贫母,我們可以大膽的猜測盒刚,Foo
的屬性foo
是一個描述器,它通過__get__()
方法來控制對foo
的訪問橘原。
根據(jù)描述器協(xié)議方法descr.__get__(self, obj, type=None) --> value
涡上,我們嘗試如下:
>>> Foo.__dict__['foo'].__get__(None,Foo)
<unbound method Foo.foo>
于是,我們驚訝的看到這個結(jié)果竟然與上一小節(jié)看到的結(jié)果相同芋酌!
這絕不是偶然。
事實(shí)上隔嫡,根據(jù)官方文檔的描述甘穿,調(diào)用Foo.foo
時温兼,Python會根據(jù)查找鏈從Foo.__dict__['foo']
開始武契,然后查找type(Foo).__dict__['foo']
,一路向上查找type(Foo)
的所有基類届垫。Foo.foo
會被轉(zhuǎn)換為Foo.__dict__['foo'].__get__(None,Foo)
全释。
也就是說,我們在代碼中使用Foo.foo
實(shí)際上會被轉(zhuǎn)化成
Foo.__dict__['foo'].__get__(None,Foo)
對于根據(jù)描述器協(xié)議方法descr.__get__(self, obj, type=None) --> value
的參數(shù)列表妄迁,由于其self
參數(shù)在這里被賦予了None
,所以沒有給定實(shí)例登淘,因此認(rèn)為是未綁定(unbound)
(當(dāng)然這是一種便于理解的描述,其根本機(jī)制請移步這里)
那么一個很簡單的推理就是:如果self
參數(shù)給定了實(shí)例對象黔州,那么,得到的就是bound method牲蜀,如下合冀。
>>> Foo.__dict__['foo'].__get__(Foo(),Foo)
<bound method Foo.foo of <__main__.Foo object at 0x7ff33b424d50>>
因此,可以有如下理解:
- 當(dāng)通過類來獲取函數(shù)屬性的時候峭判,得到的是非綁定方法對象
- 當(dāng)通過實(shí)例來獲取函數(shù)屬性的時候,得到的是綁定方法對象
3. methods, static method and class method
如果有使用Python方法的經(jīng)驗(yàn)林螃,那么一定注意過self
的使用俺泣,請看下面這個例子:
>>> class Foo(object):
... def foo():
... print 'call foo'
... def foo_one(self):
... print 'call foo_one'
...
>>> Foo.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)
>>> Foo().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes no arguments (1 given)
>>> Foo.foo_one()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method foo_one() must be called with Foo instance as first argument (got nothing instead)
>>> Foo().foo_one()
call foo_one
這個例子定義了兩個method:foo()
和foo_one(self)
伏钠。
可以看到,同樣使用類名.方法名()調(diào)用時熟掂,所報的錯誤相同。但是在使用實(shí)例名.方法名()調(diào)用時素跺,foo_one
是可以調(diào)用成功的誉券。
為什么呢?
原因在于當(dāng)使用Foo().foo_one()
調(diào)用時踩验,Python做了如下修改:
>>> Foo.foo_one(Foo())
call foo_one
將實(shí)例Foo()
作為第一個參數(shù)傳遞進(jìn)去,因此晰甚,函數(shù)foo_one(self)
調(diào)用成功。這也解釋了為什么Foo().foo()
調(diào)用不成功蓖捶。
因?yàn)?code>foo的定義為foo()
扁远,當(dāng)調(diào)用Foo().foo()
時,Python做了如下修改:
>>> Foo.foo(Foo())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes no arguments (1 given)
傳入了一個參數(shù)Foo()
畅买,所以會出現(xiàn)foo() takes no arguments (1 given)
的錯誤。
我曾經(jīng)看到有的人把foo()
這種參數(shù)列表中沒有self
的方法稱為類方法帝火,而把帶有self
的方法稱為實(shí)例方法湃缎,根據(jù)上面的描述可以發(fā)現(xiàn),這種劃分是錯誤的九巡。
那么蹂季,Python中有沒有類方法呢?
答案是偿洁,有。那么如何定義一個類方法呢神凑?
Pyhon 類方法
還是使用上面的例子
>>> class Foo(object):
... @classmethod #定義類方法要點(diǎn)1
... def foo(cls): #定義類方法要點(diǎn)2
... print 'call foo'
...
>>> Foo.foo()
call foo
>>> Foo().foo()
call foo
定義類方法需要注意兩點(diǎn):1. 添加@classmethod
;2. 添加cls
參數(shù)
這樣定義的類方法可以通過類名.方法名()的形式調(diào)用鹃唯,也可以通過實(shí)例.方法名()的形式調(diào)用。
看到這里會發(fā)現(xiàn)坡慌,在Python中定義方法,總要帶兩個參數(shù)self
或者cls
跪者。其中通過self
限定的method必須使用實(shí)例才能調(diào)用。
那么很自然的一個疑問是逗概,能不能定義不包含self
及cls
的方法呢忘衍?像最開始的例子中foo()
那樣。答案是有的枚钓,辦法就是加@staticmethod
修飾器。
這種被@staticmethod
修飾器修飾的方法星掰,稱為靜態(tài)方法
Python 靜態(tài)方法
除了類方法嫩舟,還有靜態(tài)方法,請看下面這個例子:
>>> class Foo(object):
... @staticmethod
... def foo():
... print 'call foo'
...
>>> Foo.foo()
call foo
>>> Foo().foo()
call foo
靜態(tài)方法可以通過類名.方法名()和實(shí)例.方法名()的形式調(diào)用威始。
查看type
結(jié)果如下:
>>> type(Foo.foo)
<type 'function'>
可以看到像街,靜態(tài)方法的類型是function
,而類方法的類型是instancemethod
脓斩。
總結(jié)
最后來總結(jié)一下:
從Python方法定義的角度出發(fā),可以分為三種:
1.第一個參數(shù)是self
;
2.第一個參數(shù)是cls
;
3.參數(shù)既不含self
也不含cls
的
對于第一種方法随静,必須通過實(shí)例.方法名()或類名.方法名(實(shí)例)的形式調(diào)用吗讶;
對于第二種,可以通過實(shí)例.方法名()或類名.方法名()的形式調(diào)用重绷,不能通過類名.方法名(實(shí)例)的形式調(diào)用;
對于第三種昭卓,方法即是普通函數(shù),但是必須通過實(shí)例.方法名()或類名.方法名()的形式調(diào)用候醒,不能通過其他形式調(diào)用