1. 程式人生 > >python @classmethod 和 @staticmethod區別,以及類中方法引數cls和self的區別

python @classmethod 和 @staticmethod區別,以及類中方法引數cls和self的區別

staticmethod

首先來看@staticmethod,這個裝飾器很好理解,就是讓類中的方法變成一個普通的函式(因為是普通函式,並沒有繫結在任何一個特定的類或者例項上。所以與不需要物件例項化就可以直接呼叫)。可以使用類或者類的例項呼叫,並且沒有任何隱含引數的傳入,所以不需要self(引數名是隨便定的)。

複製程式碼

>>> class C(object):
...     @staticmethod
...     def add(a,b):
...             return a+b
...     def get_weight(self):
...             return self.add(1,2)
... 
>>> C.add
<function add at 0x1d32668>
>>> C().add
<function add at 0x1d32668>
>>> C.get_weight
<unbound method C.get_weight>

複製程式碼

總結:

1、當一個函式邏輯上屬於一個類又不依賴與類的屬性的時候,可以使用 @staticmethod。

2、使用 @staticmethod 可以避免每次使用的時都會建立一個物件的開銷。

3、@staticmethod 可以使用類和類的例項呼叫。但是不依賴於類和類的例項的狀態。

classmethod

再看@classmethod,我們對比下加與不加裝飾前後函式

不加裝飾前

複製程式碼

>>> class C(object):
...     weight = 12
...     def get_weight(self):
...             print self
... 
>>> C.get_weight
<unbound method C.get_weight>
>>> C().get_weight
<bound method C.get_weight of <__main__.C object at 0x25d7b10>>
>>> C().get_weight()
<__main__.C object at 0x25d7a50>
>>> myc = C()
>>> myc.get_weight
<bound method C.get_weight of <__main__.C object at 0x25d7a50>>
>>> myc.get_weight()
<__main__.C object at 0x25d7a50>

複製程式碼

通過例子知道,C().get_weight和myc.get_weight都是繫結在物件C的一個例項上的。(e.g. <bound method C.get_weight of <__main__.C object at 0x25d7b10>>)

順便通過下面的程式碼,看下get_weight的self到底接收的是什麼:

複製程式碼

#繼續上面的程式碼
>>> C.get_weight()        #異常報錯
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method get_weight() must be called with C instance as first argument (got nothing instead)
>>> C.get_weight(C())    #將C的一個例項顯示傳遞給self
<__main__.C object at 0x25d7b50>
>>> myc.get_weight()    #myc.get_weight()隱藏了第一個引數myc
<__main__.C object at 0x25d7a50>
>>> 

複製程式碼

呼叫的例項隱藏的作為一個引數self傳遞過去了,self 只是一個普通的引數名稱(引數名是隨便定的),不是關鍵字。

加@classmethod裝飾後

複製程式碼

>>> class C(object):
...     weight = 12
...     @classmethod
...     def get_weight(cls):
...             print cls
... 
>>> C.get_weight
<bound method type.get_weight of <class '__main__.C'>>
>>> C().get_weight
<bound method type.get_weight of <class '__main__.C'>>
>>> myc = C()
>>> myc.get_weight
<bound method type.get_weight of <class '__main__.C'>>
>>> myc.get_weight()
<class '__main__.C'>
>>> C.get_weight()
<class '__main__.C'>

複製程式碼

可以看出,C類和C類的例項都能呼叫 get_weight 而且呼叫結果完全一樣。 我們看到 weight 是屬於 C類的屬性,當然也是C的例項的屬性(元物件__get__機制)。

再看一下get_weight的引數cls到底接收的是什麼,可以看到C.get_weight()和myc.get_weight()接收的都是C類(e.g. <class '__main__.C'> )而不是C類的例項,cls只是一個普通的函式引數,呼叫時隱含的傳遞過去。

總結:

1、classmethod 是類物件與函式的結合。

2、可以使用類和類的例項呼叫,但是都是將類作為隱含引數傳遞過去。

3、使用類來呼叫 classmethod 可以避免將類例項化的開銷。

==============================================================================

補充:

@staticmethod 裝飾器會讓 foo 的 __get__ 返回一個函式,而不是一個方法。看下面的例子

複製程式碼

>>> class C(object):
...     def foo(self):
...         pass
...
>>> C.foo
<unbound method C.foo>
>>> C().foo
<bound method C.foo of <__main__.C object at 0xb76ddcac>>
>>>

複製程式碼

所謂 bound method ,就是方法物件的第一個函式引數繫結為了這個類的例項(所謂 bind )。這也是那個 self 的由來。

那,我們加入@staticmethod之後:

複製程式碼

>>> class C(object):
...     @staticmethod
...     def foo():
...         pass
...
>>> C.foo
<function foo at 0xb76d056c>
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0xb76d056c>

複製程式碼

裝飾器會讓 foo 的 __get__ 返回一個函式,而不是一個方法。

參考資料: