標籤:

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 |