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__ 返回一個函式,而不是一個方法。