什麼是method?
function就是可以通過名字可以呼叫的一段程式碼,我們可以傳引數進去,得到返回值。所有的引數都是明確的傳遞過去的。
method是function與物件的結合。我們呼叫一個方法的時候,有些引數是隱含的傳遞過去的。下文會詳細介紹。
instancemethod
1 2 3 4 5 6 7 8 9 |
In [5]: class Human(object): ...: def __init__(self, weight): ...: self.weight = weight ...: def get_weight(self): ...: return self.weight ...: In [6]: Human.get_weight Out[6]: <unbound method Human.get_weight> |
這告訴我們get_weight是一個沒有被繫結方法,什麼叫做未繫結呢?繼續看下去。
1 2 3 4 5 6 7 |
In [7]: Human.get_weight() --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /home/yao/learn/insight_python/<ipython-input-7-a2b2c5cd2f8d> in <module>() ----> 1 Human.get_weight() TypeError: unbound method get_weight() must be called with Human instance as first argument (got nothing instead) |
未繫結的方法必須使用一個Human例項作為第一個引數來呼叫啊。那我們來試試
1 2 |
In [10]: Human.get_weight(Human(45)) Out[10]: 45 |
果然成功了,但是一般情況下我們習慣這麼使用。
1 2 3 4 |
In [11]: person = Human(45) In [12]: person.get_weight() Out[12]: 45 |
這兩種方式的結果一模一樣。我們看下官方文件是怎麼解釋這種現象的。
1 2 3 4 5 6 |
When an instance attribute is referenced that isn’t a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list. |
原來我們常用的呼叫方法(person.get_weight())是把呼叫的例項隱藏的作為一個引數self傳遞過去了, self 只是一個普通的引數名稱,不是關鍵字。
1 2 3 4 5 |
In [13]: person.get_weight Out[13]: <bound method Human.get_weight of <__main__.Human object at 0x8e13bec>> In [14]: person Out[14]: <__main__.Human at 0x8e13bec> |
我們看到get_weight被繫結在了 person 這個例項物件上。
總結下
- instance method 就是例項物件與函式的結合。
- 使用類呼叫,第一個引數明確的傳遞過去一個例項。
- 使用例項呼叫,呼叫的例項被作為第一個引數被隱含的傳遞過去。
classmethod
1 2 3 4 5 6 7 8 |
In [1]: class Human(object): ...: weight = 12 ...: @classmethod ...: def get_weight(cls): ...: return cls.weight In [2]: Human.get_weight Out[2]: <bound method type.get_weight of <class '__main__.Human'>> |
我們看到get_weight是一個繫結在 Human 這個類上的method。呼叫下看看
1 2 3 4 |
In [3]: Human.get_weight() Out[3]: 12 In [4]: Human().get_weight() Out[4]: 12 |
類和類的例項都能呼叫 get_weight 而且呼叫結果完全一樣。
我們看到 weight 是屬於 Human 類的屬性,當然也是 Human 的例項的屬性。那傳遞過去的引數 cls 是類還是例項呢?
1 2 3 4 5 6 7 8 9 10 11 |
In [1]: class Human(object): ...: weight = 12 ...: @classmethod ...: def get_weight(cls): ...: print cls In [2]: Human.get_weight() <class '__main__.Human'> In [3]: Human().get_weight() <class '__main__.Human'> |
我們看到傳遞過去的都是 Human 類,不是 Human 的例項,兩種方式呼叫的結果沒有任何區別。cls 只是一個普通的函式引數,呼叫時被隱含的傳遞過去。
總結起來
- classmethod 是類物件與函式的結合。
- 可以使用類和類的例項呼叫,但是都是將類作為隱含引數傳遞過去。
- 使用類來呼叫 classmethod 可以避免將類例項化的開銷。
staticmethod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
In [1]: class Human(object): ...: @staticmethod ...: def add(a, b): ...: return a + b ...: def get_weight(self): ...: return self.add(1, 2) In [2]: Human.add Out[2]: <function __main__.add> In [3]: Human().add Out[3]: <function __main__.add> In [4]: Human.add(1, 2) Out[4]: 3 In [5]: Human().add(1, 2) Out[5]: 3 |
我們看到 add 在無論是類還是例項上都只是一個普通的函式,並沒有繫結在任何一個特定的類或者例項上。可以使用類或者類的例項呼叫,並且沒有任何隱含引數的傳入。
1 2 3 4 5 |
In [6]: Human().add is Human().add Out[6]: True In [7]: Human().get_weight is Human().get_weight Out[7]: False |
add 在兩個例項上也是同一個物件。instancemethod 就不一樣了,每次都會建立一個新的 get_weight 物件。
總結下
- 當一個函式邏輯上屬於一個類又不依賴與類的屬性的時候,可以使用 staticmethod。
- 使用 staticmethod 可以避免每次使用的時都會建立一個物件的開銷。
- staticmethod 可以使用類和類的例項呼叫。但是不依賴於類和類的例項的狀態。