本篇主要總結Python中綁定方法對象(Bound method object)和未綁定方法對象(Unboud method object)的區別和聯繫。
主要目的是分清楚這兩個極容易混淆的概念,順便將Python的靜態方法,類方法及實例方法加以說明html
OK,下面開始python
類中所定義的函數稱爲方法
舉例:segmentfault
>>>class Foo(object): ... def foo(): ... print 'call foo'
而後使人困惑的地方就來了:
當你嘗試使用類名.方法名調用函數foo時,會出現以下錯誤函數
>>> 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的實例(instance)來調用,OK,因而調用以下:翻譯
>>> Foo().foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: foo() takes no arguments (1 given)
-.-!!!
估計脾氣很差的看到作到這裏想要罵街了。
由於從字面上看Foo( ).foo( )並無傳遞任何參數,而報錯信息卻顯示(1 given)。code
在Python中一切皆對象,方法是函數,因此咱們來仔細查看一下函數對象foohtm
>>> Foo.foo <unbound method Foo.foo> >>> Foo().foo <bound method Foo.foo of <__main__.Foo object at 0x7ff33b424d50>>
咦~,發現一個有趣的現象:
經過類名Foo獲取類函數屬性foo時,獲得的是unbound method object,經過實例Foo()獲取類的函數屬性foo時,獲得的是bound method object。
在來看看這兩個對象的類型:對象
>>> type(Foo.foo) <type 'instancemethod'> >>> type(Foo().foo) <type 'instancemethod'>
因而咱們產生了更大的疑問:爲何一樣是實例方法(instancemethod),獲取方式的不一樣,會致使得到不一樣的對象呢?blog
下面讓咱們來一層層揭開這個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是一個函數對象,根據上一小節最後一個例子的信息,咱們發現,foo是有綁定行爲的。
在Python中使用描述器(有翻譯的連接)來表示具備「綁定」行爲的對象屬性,使用描述器協議方法來控制對具備綁定行爲屬性的訪問,這些描述器協議方法包括:__get__()、__set__()和__delete__()。
根據上面這段難以讓人理解的描述,咱們能夠大膽的猜想,Foo的屬性foo是一個描述器,它經過__get__()方法來控制對foo的訪問。
根據描述器協議方法descr.__get__(self, obj, type=None) --> value,咱們嘗試以下:
>>> Foo.__dict__['foo'].__get__(None,Foo) <unbound method Foo.foo>
因而,咱們驚訝的看到這個結果居然與上一小節看到的結果相同!
這毫不是偶然。
事實上,根據官方文檔的描述,調用Foo.foo時,Python會根據查找鏈從Foo.__dict__['foo']開始,而後查找type(Foo).__dict__['foo'],一路向上查找type(Foo)的全部基類。Foo.foo會被轉換爲Foo.__dict__['foo'].__get__(None,Foo)。
也就是說,咱們在代碼中使用Foo.foo實際上會被轉化成
Foo.__dict__['foo'].__get__(None,Foo)
對於根據描述器協議方法descr.__get__(self, obj, type=None) --> value的參數列表,因爲其self參數在這裏被賦予了None,因此沒有給定實例,所以認爲是未綁定(unbound)
(固然這是一種便於理解的描述,其根本機制請移步這裏)
那麼一個很簡單的推理就是:若是self參數給定了實例對象,那麼,獲得的就是bound method,以下。
>>> Foo.__dict__['foo'].__get__(Foo(),Foo) <bound method Foo.foo of <__main__.Foo object at 0x7ff33b424d50>>
所以,能夠有以下理解:
當經過類來獲取函數屬性的時候,獲得的是非綁定方法對象
當經過實例來獲取函數屬性的時候,獲得的是綁定方法對象
若是有使用Python方法的經驗,那麼必定注意過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)。
能夠看到,一樣使用類名.方法名()調用時,所報的錯誤相同。可是在使用實例名.方法名()調用時,foo_one是能夠調用成功的。
爲何呢?
緣由在於當使用Foo().foo_one()調用時,Python作了以下修改:
>>> Foo.foo_one(Foo()) call foo_one
將實例Foo()做爲第一個參數傳遞進去,所以,函數foo_one(self)調用成功。這也解釋了爲何Foo().foo()調用不成功。
由於foo的定義爲foo(),當調用Foo().foo()時,Python作了以下修改:
>>> Foo.foo(Foo()) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: foo() takes no arguments (1 given)
傳入了一個參數Foo(),因此會出現foo() takes no arguments (1 given)的錯誤。
我曾經看到有的人把foo()這種參數列表中沒有self的方法稱爲類方法,而把帶有self的方法稱爲實例方法,根據上面的描述能夠發現,這種劃分是錯誤的。
那麼,Python中有沒有類方法呢?
答案是,有。那麼如何定義一個類方法呢?
仍是使用上面的例子
>>> class Foo(object): ... @classmethod #定義類方法要點1 ... def foo(cls): #定義類方法要點2 ... print 'call foo' ... >>> Foo.foo() call foo >>> Foo().foo() call foo
定義類方法須要注意兩點:1. 添加@classmethod;2. 添加cls參數
這樣定義的類方法能夠經過類名.方法名()的形式調用,也能夠經過實例.方法名()的形式調用。
看到這裏會發現,在Python中定義方法,總要帶兩個參數self或者cls。其中經過self限定的method必須使用實例才能調用。
那麼很天然的一個疑問是,能不能定義不包含self及cls的方法呢?像最開始的例子中foo()那樣。答案是有的,辦法就是加@staticmethod修飾器。
這種被@staticmethod修飾器修飾的方法,稱爲靜態方法
除了類方法,還有靜態方法,請看下面這個例子:
>>> class Foo(object): ... @staticmethod ... def foo(): ... print 'call foo' ... >>> Foo.foo() call foo >>> Foo().foo() call foo
靜態方法能夠經過類名.方法名()和實例.方法名()的形式調用。
查看type結果以下:
>>> type(Foo.foo) <type 'function'>
能夠看到,靜態方法的類型是function,而類方法的類型是instancemethod。
最後來總結一下:
從Python方法定義的角度出發,能夠分爲三種:
1.第一個參數是self;
2.第一個參數是cls;
3.參數既不含self也不含cls的
對於第一種方法,必須經過實例.方法名()或類名.方法名(實例)的形式調用;
對於第二種,能夠經過實例.方法名()或類名.方法名()的形式調用,不能經過類名.方法名(實例)的形式調用;
對於第三種,方法便是普通函數,可是必須經過實例.方法名()或類名.方法名()的形式調用,不能經過其餘形式調用
轉載自:簡書-_Zhao_ -Python-Unbound/Bound method object
這篇博文講解的很是清楚,值得一看