Python進階課程筆記(四)
我們繼續來看那些Callable的Python類型。
5. Class method和Static method
這部分其實不能算Python的進階內容了,可以說是基礎內容。在初學Python的時候我就有過一些疑惑,@classmethod和@staticmethod修飾的函數看上去似乎沒有什麼區別,都是通過類來調用(當然對象也可以)。本小節內容稍微看一下兩者的區別,首先是class method,因為比較簡單,直接看代碼例子和結果吧。
class A(object):n @classmethodn def foo(self):n passnn def bar(self):n passnna = A()nprint A.foonprint a.foonnm1 = A.foonm2 = a.foonprint 1, id(m1)nprint 2, id(m2)n
運行結果如下:
<bound method type.foo of <class __main__.A>>n<bound method type.foo of <class __main__.A>>n1 37346800n2 37266616n
可以看到,我們把A.foo列印處理,是一個bound method對象,通過對象a來訪問也是一樣的,我們學著bound method的方式來分析看,m1和m2的id是不同,在理解了第4小節的內容之後這裡就比較容易理解了。
思考: Class method是綁定了什麼東西的bound method呢?
通過查看bound method對象的im_self屬性就可以看到它所綁定的對象了,我們添加一些代碼來分析。
print m1.im_selfnprint m2.im_selfnnm3 = a.barnprint m3.im_selfn<class __main__.A>n<class __main__.A>n<__main__.A object at 0x02440030>n
結論: Class Method是綁定了類對象的bound method。
需要額外注意的是,def foo(self):這裡對於class method的定義是不好的,具有一定的誤導性,這裡的self不再是類的實例對象了,而是一個class對象,因此通常寫成def foo(cls):或者def foo(klass)更清晰。
Static method是否也是bound method呢?我們直接來看分析代碼。
class A(object):n @staticmethodn def foo(self):n passnna = A()nprint A.foonprint a.foonnm1 = A.foonm2 = a.foonprint 1, id(m1)nprint 2, id(m2)n
輸出結果為:
<function foo at 0x023FA4B0>n<function foo at 0x023FA4B0>n1 37725360n2 37725360n
結論: Static method就是在類的命名空間中的一個普通函數。
6. Functors和Operators
在C++中,重寫了()操作符的類,它們實例化的對象就是一個functor,在Python中,重寫了__call__方法的類的實例對象是一個functor。這裡具體的內容就不再詳述了,屬於Python中比較基本的內容。
類似的,關於操作符(Operators),在Python中也是重載一些對應函數,比如+就是重載__add__函數。這裡需要說的是一個性能問題,我們來看一個例子:import timeitnndef foo(a, b):n return a + bnndef bar(a, b):n return a.__add__(b)nnnn = 5000000nprint timeit.Timer(foo(1, 2), from __main__ import foo).timeit(n)nprint timeit.Timer(bar(1, 2), from __main__ import bar).timeit(n)nnprint timeit.Timer(foo("1", "2"), from __main__ import foo).timeit(n)nprint timeit.Timer(bar("1", "2"), from __main__ import bar).timeit(n)n
輸出結果如下:
0.689781276848n1.40617516723n0.806692302726n1.4724794197n
原理上,+的操作符是調用__add__方法來進行處理,那為什麼直接調用__add__方法反而會慢呢?我們使用dis模塊來看一下Python代碼翻譯成Python虛擬機的指令的區別。
import disndis.dis(foo)nprint - * 50ndis.dis(bar)n
輸出結果如下:
4 0 LOAD_FAST 0 (a)n 3 LOAD_FAST 1 (b)n 6 BINARY_ADD n 7 RETURN_VALUE n--------------------------------------------------n 7 0 LOAD_FAST 0 (a)n 3 LOAD_ATTR 0 (__add__)n 6 LOAD_FAST 1 (b)n 9 CALL_FUNCTION 1n 12 RETURN_VALUEn
dis模塊的dis方法可以列印出傳入函數的虛擬機指令,可以看到foo函數使用+,並沒有調用__add__方法,而是用了一個BINARY_ADD指令來直接進行加法。這就是它比直接調用__add__方法快的原因。
總結:對於Python的內建類型,操作符比直接調用對應的函數要好,Python可能會使用對應的二進位操作進行優化。
思考: 對於用戶自定義的類型呢,效率哪個更高?
我們來看例子的代碼:
import timeitnndef foo(a, b):n return a + bnndef bar(a, b):n return a.__add__(b)nnclass Number(object):n def __init__(self, v):n self._value = vnn def __add__(self, v):n return self._value + v._valuennn1 = Number(1)nn2 = Number(2)nprint n1 + n2nnn = 1000000nprint timeit.Timer(foo(Number(1), Number(2)), from __main__ import foo, Number).timeit(n)nprint timeit.Timer(bar(Number(1), Number(2)), from __main__ import bar, Number).timeit(n)n
輸出結果:
3n1.34897905469n1.26909055996n
看上去,直接調用__add__方法反而更快一些,但是差別整體不大。這是因為+操作符會進行優化嘗試,需要額外的時間消耗。不過呢,這點時間差別比較小,而且直接使用類似__add__的方法調用對於代碼的可讀性維護性會帶來一定的困擾,因此大部分情況下,直接使用操作符會更好。
2016年7月8日晚於杭州網易大廈
推薦閱讀:
※Scrapy學習實例(二)採集無限滾動頁面
※特徵工程總結:R與Python的比較實現
※Python 初學者最容易犯的幾個錯誤。
※markdown for academia
※Python 字母行轉序號應該怎麼做?
TAG:Python |