關(guān)于python在函數(shù)里計(jì)時(shí)的信息

在python程序中,會(huì)出現(xiàn)這樣的錯(cuò)誤

就是告訴你有錯(cuò)誤。你for之前那個(gè)clock()是什么?哪里來的?

10年積累的成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有桃源免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

就算你把它刪除掉,最后那個(gè)print里用的clock又怎么辦?

如果你想使用time.clock()做計(jì)時(shí):

你需要在文件開始引入這個(gè)包里的clock:

from?time?import?clock

然后,在開始計(jì)時(shí)的地方保存clock()的值:

t?=?clock()

在結(jié)束計(jì)時(shí)的地方計(jì)算

clock()?-?t

即為用時(shí)。

如果要使用time.perf_counter()做計(jì)時(shí):

修改相應(yīng)的import語(yǔ)句為

from?time?import?perf_counter?as?clock

其它代碼不需要修改即可使用。

但有一點(diǎn)需要注意的是,perf_counter自第一次引用后,它就開始計(jì)時(shí),之后無論調(diào)用多少次,它都是返回當(dāng)前時(shí)間到開始計(jì)時(shí)的時(shí)間差,這會(huì)產(chǎn)生一個(gè)問題:

如果在其它模塊中導(dǎo)入了它到全局范圍,那么,所有模塊中使用此函數(shù)的將都使用同一個(gè)計(jì)時(shí)器,這會(huì)讓使用它得到的時(shí)長(zhǎng)超出期望。

一般情況下,這是用來測(cè)試一段程序的用時(shí)的,應(yīng)當(dāng)避免使用影響到其它環(huán)境或受其它環(huán)境影響的方式,所以建議你使用第一種方式去處理這個(gè)問題

如何讓 Python 像 Julia 一樣快地運(yùn)行

Julia 與 Python

的比較

我是否應(yīng)丟棄 Python 和其他語(yǔ)言,使用 Julia 執(zhí)行技術(shù)計(jì)算?在看到 上的基準(zhǔn)測(cè)試后,人們一定會(huì)這么想。Python

和其他高級(jí)語(yǔ)言在速度上遠(yuǎn)遠(yuǎn)有些落后。但是,我想到的第一個(gè)問題有所不同:Julia 團(tuán)隊(duì)能否以最適合 Python 的方式編寫 Python 基準(zhǔn)測(cè)試?

我對(duì)這種跨語(yǔ)言比較的觀點(diǎn)是,應(yīng)該根據(jù)要執(zhí)行的任務(wù)來定義基準(zhǔn)測(cè)試,然后由語(yǔ)言專家編寫執(zhí)行這些任務(wù)的最佳代碼。如果代碼全由一個(gè)語(yǔ)言團(tuán)隊(duì)編寫,則存在其他語(yǔ)言未得到最佳使用的風(fēng)險(xiǎn)。

Julia 團(tuán)隊(duì)有一件事做得對(duì),那就是他們將他們使用的代碼發(fā)布到了 github 上。具體地講,Python 代碼可在此處找到。

第一眼看到該代碼,就可以證實(shí)我所害怕的偏見。該代碼是以 C 風(fēng)格編寫的,在數(shù)組和列表上大量使用了循環(huán)。這不是使用 Python 的最佳方式。

我不會(huì)責(zé)怪 Julia 團(tuán)隊(duì),因?yàn)槲液軆?nèi)疚自己也有同樣的偏見。但我受到了殘酷的教訓(xùn):付出任何代價(jià)都要避免數(shù)組或列表上的循環(huán),因?yàn)樗鼈兇_實(shí)會(huì)拖慢 Python

中的速度,請(qǐng)參閱 Python 不是 C。

考慮到對(duì) C 風(fēng)格的這種偏見,一個(gè)有趣的問題(至少對(duì)我而言)是,我們能否改進(jìn)這些基準(zhǔn)測(cè)試,更好地使用 Python 及其工具?

在我給出答案之前,我想說我絕不會(huì)試圖貶低 Julia。在進(jìn)一步開發(fā)和改進(jìn)后,Julia 無疑是一種值得關(guān)注的語(yǔ)言。我只是想分析 Python

方面的事情。實(shí)際上,我正在以此為借口來探索各種可用于讓代碼更快運(yùn)行的 Python 工具。

在下面的內(nèi)容中,我使用 Docker 鏡像在 Jupyter Notebook 中使用 Python 3.4.3,其中已安裝了所有的 Python 科學(xué)工具組合。我還會(huì)通過

Windows 機(jī)器上的 Python 2.7.10,使用 Anaconda 來運(yùn)行代碼。計(jì)時(shí)是對(duì) Python 3.4.3 執(zhí)行的。包含下面的所有基準(zhǔn)測(cè)試的完整代碼的 Notebook 可在此處找到。

鑒于各種社交媒體上的評(píng)論,我添加了這樣一句話:我沒有在這里使用 Python 的替代性實(shí)現(xiàn)。我沒有編寫任何 C

代碼:如果您不信,可試試尋找分號(hào)。本文中使用的所有工具都是 Anaconda 或其他發(fā)行版中提供的標(biāo)準(zhǔn)的 Cython 實(shí)現(xiàn)。下面的所有代碼都在單個(gè) Notebook中運(yùn)行。

我嘗試過使用來自 github 的 Julia 微性能文件,但不能使用 Julia 0.4.2 原封不動(dòng)地運(yùn)行它。我必須編輯它并將 @timeit 替換為

@time,它才能運(yùn)行。在對(duì)它們計(jì)時(shí)之前,我還必須添加對(duì)計(jì)時(shí)函數(shù)的調(diào)用,否則編譯時(shí)間也將包含在內(nèi)。我使用的文件位于此處。我在用于運(yùn)行 Python 的同一個(gè)機(jī)器上使用 Julia 命令行接口運(yùn)行它。

回頁(yè)首

計(jì)時(shí)代碼

Julia 團(tuán)隊(duì)使用的第一項(xiàng)基準(zhǔn)測(cè)試是 Fibonacci 函數(shù)的一段簡(jiǎn)單編碼。

def fib(n):

if n2:

return n

return fib(n-1)+fib(n-2)

此函數(shù)的值隨 n 的增加而快速增加,例如:

fib(100) = 354224848179261915075

可以注意到,Python 任意精度 (arbitrary precision) 很方便。在 C 等語(yǔ)言中編寫相同的函數(shù)需要花一些編碼工作來避免整數(shù)溢出。在 Julia

中,需要使用 BigInt 類型。

所有 Julia 基準(zhǔn)測(cè)試都與運(yùn)行時(shí)間有關(guān)。這是 Julia 中使用和不使用 BigInt 的計(jì)時(shí):

0.000080 seconds (149 allocations:10.167 KB)

0.012717 seconds (262.69 k allocations:4.342 MB)

在 Python Notebook 中獲得運(yùn)行時(shí)間的一種方式是使用神奇的 %timeit。例如,在一個(gè)新單元中鍵入:

%timeit fib(20)

執(zhí)行它會(huì)獲得輸出:

100 loops, best of 3:3.33 ms per loop

這意味著計(jì)時(shí)器執(zhí)行了以下操作:

運(yùn)行 fib(20) 100 次,存儲(chǔ)總運(yùn)行時(shí)間

運(yùn)行 fib(20) 100 次,存儲(chǔ)總運(yùn)行時(shí)間

運(yùn)行 fib(20) 100 次,存儲(chǔ)總運(yùn)行時(shí)間

從 3 次運(yùn)行中獲取最小的運(yùn)行時(shí)間,將它除以 100,然后輸出結(jié)果,該結(jié)果就是 fib(20) 的最佳運(yùn)行時(shí)間

這些循環(huán)的大小(100 次和 3 次)會(huì)由計(jì)時(shí)器自動(dòng)調(diào)整。可能會(huì)根據(jù)被計(jì)時(shí)的代碼的運(yùn)行速度來更改循環(huán)大小。

Python 計(jì)時(shí)與使用了 BigInt 時(shí)的 Julia 計(jì)時(shí)相比出色得多:3 毫秒與 12 毫秒。在使用任意精度時(shí),Python 的速度是 Julia 的 4

倍。

但是,Python 比 Julia 默認(rèn)的 64 位整數(shù)要慢。我們看看如何在 Python 中強(qiáng)制使用 64 位整數(shù)。

回頁(yè)首

使用 Cython 編譯

一種編譯方式是使用 Cython 編譯器。這個(gè)編譯器是使用 Python

編寫的。它可以通過以下命令安裝:

pip install Cython

如果使用 Anaconda,安裝會(huì)有所不同。因?yàn)榘惭b有點(diǎn)復(fù)雜,所以我編寫了一篇相關(guān)的博客文章:將 Cython For Anaconda 安裝在 Windows 上

安裝后,我們使用神奇的 %load_ext 將 Cython 加載到 Notebook 中:

%load_ext Cython

然后就可以在我們的 Notebook 中編譯代碼。我們只需要將想要編譯的代碼放在一個(gè)單元中,包括所需的導(dǎo)入語(yǔ)句,使用神奇的 %%cython 啟動(dòng)該單元:

%%cython

def fib_cython(n):

if n2:

return n

return fib_cython(n-1)+fib_cython(n-2)

執(zhí)行該單元會(huì)無縫地編譯這段代碼。我們?yōu)樵摵瘮?shù)使用一個(gè)稍微不同的名稱,以反映出它是使用 Cython

編譯的。當(dāng)然,一般不需要這么做。我們可以將之前的函數(shù)替換為相同名稱的已編譯函數(shù)。

對(duì)它計(jì)時(shí)會(huì)得到:

1000 loops, best of 3:1.22 ms per loop

哇,幾乎比最初的 Python 代碼快 3 倍!我們現(xiàn)在比使用 BigInt 的 Julia 快 100 倍。

我們還可以嘗試靜態(tài)類型。使用關(guān)鍵字 cpdef 而不是 def 來聲明該函數(shù)。它使我們能夠使用相應(yīng)的 C 類型來鍵入函數(shù)的參數(shù)。我們的代碼變成了:

%%cython

cpdef long fib_cython_type(long n):

if n2:

return n

return fib_cython_type(n-1)+fib_cython_type(n-2)

執(zhí)行該單元后,對(duì)它計(jì)時(shí)會(huì)得到:

10000 loops, best of 3:36 μs per loop

太棒了,我們現(xiàn)在只花費(fèi)了 36 微秒,比最初的基準(zhǔn)測(cè)試快約 100 倍!這與 Julia 所花的 80 毫秒相比更出色。

有人可能會(huì)說,靜態(tài)類型違背了 Python

的用途。一般來講,我比較同意這種說法,我們稍后將查看一種在不犧牲性能的情況下避免這種情形的方法。但我并不認(rèn)為這是一個(gè)問題。Fibonacci

函數(shù)必須使用整數(shù)來調(diào)用。我們?cè)陟o態(tài)類型中失去的是 Python 所提供的任意精度。對(duì)于 Fibonacci,使用 C 類型 long

會(huì)限制輸入?yún)?shù)的大小,因?yàn)樘蟮膮?shù)會(huì)導(dǎo)致整數(shù)溢出。

請(qǐng)注意,Julia 計(jì)算也是使用 64 位整數(shù)執(zhí)行的,因此將我們的靜態(tài)類型版本與 Julia 的對(duì)比是公平的。

回頁(yè)首

緩存計(jì)算

我們?cè)诒A?Python 任意精度的情況下能做得更好。fib 函數(shù)重復(fù)執(zhí)行同一種計(jì)算許多次。例如,fib(20) 將調(diào)用 fib(19) 和

fib(18)。fib(19) 將調(diào)用 fib(18) 和 fib(17)。結(jié)果 fib(18) 被調(diào)用了兩次。簡(jiǎn)單分析表明,fib(17) 將被調(diào)用 3

次,fib(16) 將被調(diào)用 5 次,等等。

在 Python 3 中,我們可以使用 functools 標(biāo)準(zhǔn)庫(kù)來避免這些重復(fù)的計(jì)算。

from functools import lru_cache as cache

@cache(maxsize=None)

def fib_cache(n):

if n2:

return n

return fib_cache(n-1)+fib_cache(n-2)

對(duì)此函數(shù)計(jì)時(shí)會(huì)得到:

1000000 loops, best of 3:910 ns per loop

速度又增加了 40 倍,比最初的 Python 代碼快約 3,600 倍!考慮到我們僅向遞歸函數(shù)添加了一條注釋,此結(jié)果非常令人難忘。

Python 2.7 中沒有提供這種自動(dòng)緩存。我們需要顯式地轉(zhuǎn)換代碼,才能避免這種情況下的重復(fù)計(jì)算。

def fib_seq(n):

if n 2:

return n

a,b = 1,0

for i in range(n-1):

a,b = a+b,a

return a

請(qǐng)注意,此代碼使用了 Python 同時(shí)分配兩個(gè)局部變量的能力。對(duì)它計(jì)時(shí)會(huì)得到:

1000000 loops, best of 3:1.77 μs per loop

我們又快了 20 倍!讓我們?cè)谑褂煤筒皇褂渺o態(tài)類型的情況下編譯我們的函數(shù)。請(qǐng)注意,我們使用了 cdef 關(guān)鍵字來鍵入局部變量。

%%cython

def fib_seq_cython(n):

if n 2:

return n

a,b = 1,0

for i in range(n-1):

a,b = a+b,a

return a

cpdef long fib_seq_cython_type(long n):

if n 2:

return n

cdef long a,b

a,b = 1,0

for i in range(n-1):

a,b = a+b,b

return a

我們可在一個(gè)單元中對(duì)兩個(gè)版本計(jì)時(shí):

%timeit fib_seq_cython(20)

%timeit fib_seq_cython_type(20)

結(jié)果為:

1000000 loops, best of 3:953 ns per loop

10000000 loops, best of 3:51.9 ns per loop

靜態(tài)類型代碼現(xiàn)在花費(fèi)的時(shí)間為 51.9 納秒,比最初的基準(zhǔn)測(cè)試快約 60,000(六萬)倍。

如果我們想計(jì)算任意輸入的 Fibonacci 數(shù),我們應(yīng)堅(jiān)持使用無類型版本,該版本的運(yùn)行速度快 3,500 倍。還不錯(cuò),對(duì)吧?

回頁(yè)首

使用 Numba 編譯

讓我們使用另一個(gè)名為 Numba 的工具。它是針對(duì)部分 Python 版本的一個(gè)即時(shí)

(jit) 編譯器。它不是對(duì)所有 Python 版本都適用,但在適用的情況下,它會(huì)帶來奇跡。

安裝它可能很麻煩。推薦使用像 Anaconda 這樣的 Python 發(fā)行版或一個(gè)已安裝了 Numba 的 Docker 鏡像。完成安裝后,我們導(dǎo)入它的 jit 編譯器:

from numba import jit

它的使用非常簡(jiǎn)單。我們僅需要向想要編譯的函數(shù)添加一點(diǎn)修飾。我們的代碼變成了:

@jit

def fib_seq_numba(n):

if n 2:

return n

(a,b) = (1,0)

for i in range(n-1):

(a,b) = (a+b,a)

return a

對(duì)它計(jì)時(shí)會(huì)得到:

1000000 loops, best of 3:225 ns per loop

比無類型的 Cython 代碼更快,比最初的 Python 代碼快約 16,000 倍!

回頁(yè)首

使用 Numpy

我們現(xiàn)在來看看第二項(xiàng)基準(zhǔn)測(cè)試。它是快速排序算法的實(shí)現(xiàn)。Julia 團(tuán)隊(duì)使用了以下 Python 代碼:

def qsort_kernel(a, lo, hi):

i = lo

j = hi

while i hi:

pivot = a[(lo+hi) // 2]

while i = j:

while a[i] pivot:

i += 1

while a[j] pivot:

j -= 1

if i = j:

a[i], a[j] = a[j], a[i]

i += 1

j -= 1

if lo j:

qsort_kernel(a, lo, j)

lo = i

j = hi

return a

我將他們的基準(zhǔn)測(cè)試代碼包裝在一個(gè)函數(shù)中:

import random

def benchmark_qsort():

lst = [ random.random() for i in range(1,5000) ]

qsort_kernel(lst, 0, len(lst)-1)

對(duì)它計(jì)時(shí)會(huì)得到:

100 loops, best of 3:18.3 ms per loop

上述代碼與 C 代碼非常相似。Cython 應(yīng)該能很好地處理它。除了使用 Cython 和靜態(tài)類型之外,讓我們使用 Numpy

數(shù)組代替列表。在數(shù)組大小較大時(shí),比如數(shù)千個(gè)或更多元素,Numpy 數(shù)組確實(shí)比

Python 列表更快。

安裝 Numpy 可能會(huì)花一些時(shí)間,推薦使用 Anaconda 或一個(gè)已安裝了 Python 科學(xué)工具組合的 Docker 鏡像。

在使用 Cython 時(shí),需要將 Numpy 導(dǎo)入到應(yīng)用了 Cython 的單元中。在使用 C 類型時(shí),還必須使用 cimport 將它作為 C 模塊導(dǎo)入。Numpy

數(shù)組使用一種表示數(shù)組元素類型和數(shù)組維數(shù)(一維、二維等)的特殊語(yǔ)法來聲明。

%%cython

import numpy as np

cimport numpy as np

cpdef np.ndarray[double, ndim=1] \

qsort_kernel_cython_numpy_type(np.ndarray[double, ndim=1] a, \

long lo, \

long hi):

cdef:

long i, j

double pivot

i = lo

j = hi

while i hi:

pivot = a[(lo+hi) // 2]

while i = j:

while a[i] pivot:

i += 1

while a[j] pivot:

j -= 1

if i = j:

a[i], a[j] = a[j], a[i]

i += 1

j -= 1

if lo j:

qsort_kernel_cython_numpy_type(a, lo, j)

lo = i

j = hi

return a

cpdef benchmark_qsort_numpy_cython():

lst = np.random.rand(5000)

qsort_kernel_cython_numpy_type(lst, 0, len(lst)-1)

對(duì) benchmark_qsort_numpy_cython() 函數(shù)計(jì)時(shí)會(huì)得到:

1000 loops, best of 3:1.32 ms per loop

我們比最初的基準(zhǔn)測(cè)試快了約 15 倍,但這仍然不是使用 Python 的最佳方法。最佳方法是使用 Numpy 內(nèi)置的 sort()

函數(shù)。它的默認(rèn)行為是使用快速排序算法。對(duì)此代碼計(jì)時(shí):

def benchmark_sort_numpy():

lst = np.random.rand(5000)

np.sort(lst)

會(huì)得到:

1000 loops, best of 3:350 μs per loop

我們現(xiàn)在比最初的基準(zhǔn)測(cè)試快 52 倍!Julia 在該基準(zhǔn)測(cè)試上花費(fèi)了 419 微秒,因此編譯的 Python 快 20%。

我知道,一些讀者會(huì)說我不會(huì)進(jìn)行同類比較。我不同意。請(qǐng)記住,我們現(xiàn)在的任務(wù)是使用主機(jī)語(yǔ)言以最佳的方式排序輸入數(shù)組。在這種情況下,最佳方法是使用一個(gè)內(nèi)置的函數(shù)。

time.sleep在python3.11中替換為

time.sleep在python3.11中替換為python。

INTRO:眾所周知,time.sleep的準(zhǔn)確率取決于操作系統(tǒng)和計(jì)算負(fù)載。 Windows 中的準(zhǔn)確性非常差。

類似于 /questions/17499837一個(gè)方法可以使用 time.clock 實(shí)現(xiàn)忙等待方法作為 time.sleep 的替代方法.這種方法會(huì)造成不必要的負(fù)載,影響系統(tǒng)中的其他模 block 。這在進(jìn)行模擬時(shí)是不可取的。

減少花在忙等待上的時(shí)間,而不是依賴 time.sleep , 一個(gè)類使用方法 select.select并利用超時(shí)屬性。

在python里用time.time判斷函數(shù)的執(zhí)行時(shí)間靠譜嗎

使用time.time來統(tǒng)計(jì)函數(shù)的執(zhí)行時(shí)間,程序只會(huì)執(zhí)行一次,存在很大的隨機(jī)因素。

timtit包就可以重復(fù)執(zhí)行函數(shù)多次,然后將多次執(zhí)行結(jié)果取平均值。相比起來更優(yōu)。

然而程序執(zhí)行時(shí)間很大程度還受計(jì)算機(jī)性能的影響,衡量程序好壞更靠譜的手段是計(jì)算時(shí)間復(fù)雜度。

如何理解Python裝飾器

理解Python中的裝飾器

@makebold

@makeitalic

def say():

return "Hello"

打印出如下的輸出:

biHelloi/b

你會(huì)怎么做?最后給出的答案是:

def makebold(fn):

def wrapped():

return "b" + fn() + "/b"

return wrapped

def makeitalic(fn):

def wrapped():

return "i" + fn() + "/i"

return wrapped

@makebold

@makeitalic

def hello():

return "hello world"

print hello() ## 返回 bihello world/i/b

現(xiàn)在我們來看看如何從一些最基礎(chǔ)的方式來理解Python的裝飾器。英文討論參考Here。

裝飾器是一個(gè)很著名的設(shè)計(jì)模式,經(jīng)常被用于有切面需求的場(chǎng)景,較為經(jīng)典的有插入日志、性能測(cè)試、事務(wù)處理等。裝飾器是解決這類問題的絕佳設(shè)計(jì),有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能。

1.1. 需求是怎么來的?

裝飾器的定義很是抽象,我們來看一個(gè)小例子。

def foo():

print 'in foo()'

foo()

這是一個(gè)很無聊的函數(shù)沒錯(cuò)。但是突然有一個(gè)更無聊的人,我們稱呼他為B君,說我想看看執(zhí)行這個(gè)函數(shù)用了多長(zhǎng)時(shí)間,好吧,那么我們可以這樣做:

import time

def foo():

start = time.clock()

print 'in foo()'

end = time.clock()

print 'used:', end - start

foo()

很好,功能看起來無懈可擊??墒堑疤鄣腂君此刻突然不想看這個(gè)函數(shù)了,他對(duì)另一個(gè)叫foo2的函數(shù)產(chǎn)生了更濃厚的興趣。

怎么辦呢?如果把以上新增加的代碼復(fù)制到foo2里,這就犯了大忌了~復(fù)制什么的難道不是最討厭了么!而且,如果B君繼續(xù)看了其他的函數(shù)呢?

1.2. 以不變應(yīng)萬變,是變也

還記得嗎,函數(shù)在Python中是一等公民,那么我們可以考慮重新定義一個(gè)函數(shù)timeit,將foo的引用傳遞給他,然后在timeit中調(diào)用foo并進(jìn)行計(jì)時(shí),這樣,我們就達(dá)到了不改動(dòng)foo定義的目的,而且,不論B君看了多少個(gè)函數(shù),我們都不用去修改函數(shù)定義了!

import time

def foo():

print 'in foo()'

def timeit(func):

start = time.clock()

func()

end =time.clock()

print 'used:', end - start

timeit(foo)

看起來邏輯上并沒有問題,一切都很美好并且運(yùn)作正常!……等等,我們似乎修改了調(diào)用部分的代碼。原本我們是這樣調(diào)用的:foo(),修改以后變成了:timeit(foo)。這樣的話,如果foo在N處都被調(diào)用了,你就不得不去修改這N處的代碼。或者更極端的,考慮其中某處調(diào)用的代碼無法修改這個(gè)情況,比如:這個(gè)函數(shù)是你交給別人使用的。

1.3. 最大限度地少改動(dòng)!

既然如此,我們就來想想辦法不修改調(diào)用的代碼;如果不修改調(diào)用代碼,也就意味著調(diào)用foo()需要產(chǎn)生調(diào)用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個(gè)參數(shù)……想辦法把參數(shù)統(tǒng)一吧!如果timeit(foo)不是直接產(chǎn)生調(diào)用效果,而是返回一個(gè)與foo參數(shù)列表一致的函數(shù)的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然后,調(diào)用foo()的代碼完全不用修改!

#-*- coding: UTF-8 -*-

import time

def foo():

print 'in foo()'

# 定義一個(gè)計(jì)時(shí)器,傳入一個(gè),并返回另一個(gè)附加了計(jì)時(shí)功能的方法

def timeit(func):

# 定義一個(gè)內(nèi)嵌的包裝函數(shù),給傳入的函數(shù)加上計(jì)時(shí)功能的包裝

def wrapper():

start = time.clock()

func()

end =time.clock()

print 'used:', end - start

# 將包裝后的函數(shù)返回

return wrapper

foo = timeit(foo)

foo()

這樣,一個(gè)簡(jiǎn)易的計(jì)時(shí)器就做好了!我們只需要在定義foo以后調(diào)用foo之前,加上foo = timeit(foo),就可以達(dá)到計(jì)時(shí)的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個(gè)例子中,函數(shù)進(jìn)入和退出時(shí)需要計(jì)時(shí),這被稱為一個(gè)橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統(tǒng)編程習(xí)慣的從上往下執(zhí)行方式相比較而言,像是在函數(shù)執(zhí)行的流程中橫向地插入了一段邏輯。在特定的業(yè)務(wù)領(lǐng)域里,能減少大量重復(fù)代碼。面向切面編程還有相當(dāng)多的術(shù)語(yǔ),這里就不多做介紹,感興趣的話可以去找找相關(guān)的資料。

這個(gè)例子僅用于演示,并沒有考慮foo帶有參數(shù)和有返回值的情況,完善它的重任就交給你了 :)

上面這段代碼看起來似乎已經(jīng)不能再精簡(jiǎn)了,Python于是提供了一個(gè)語(yǔ)法糖來降低字符輸入量。

import time

def timeit(func):

def wrapper():

start = time.clock()

func()

end =time.clock()

print 'used:', end - start

return wrapper

@timeit

def foo():

print 'in foo()'

foo()

重點(diǎn)關(guān)注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價(jià),千萬不要以為@有另外的魔力。除了字符輸入少了一些,還有一個(gè)額外的好處:這樣看上去更有裝飾器的感覺。

-------------------

要理解python的裝飾器,我們首先必須明白在Python中函數(shù)也是被視為對(duì)象。這一點(diǎn)很重要。先看一個(gè)例子:

def shout(word="yes") :

return word.capitalize()+" !"

print shout()

# 輸出 : 'Yes !'

# 作為一個(gè)對(duì)象,你可以把函數(shù)賦給任何其他對(duì)象變量

scream = shout

# 注意我們沒有使用圓括號(hào),因?yàn)槲覀儾皇窃谡{(diào)用函數(shù)

# 我們把函數(shù)shout賦給scream,也就是說你可以通過scream調(diào)用shout

print scream()

# 輸出 : 'Yes !'

# 還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數(shù)

del shout

try :

print shout()

except NameError, e :

print e

#輸出 : "name 'shout' is not defined"

print scream()

# 輸出 : 'Yes !'

我們暫且把這個(gè)話題放旁邊,我們先看看python另外一個(gè)很有意思的屬性:可以在函數(shù)中定義函數(shù):

def talk() :

# 你可以在talk中定義另外一個(gè)函數(shù)

def whisper(word="yes") :

return word.lower()+"...";

# ... 并且立馬使用它

print whisper()

# 你每次調(diào)用'talk',定義在talk里面的whisper同樣也會(huì)被調(diào)用

talk()

# 輸出 :

# yes...

# 但是"whisper" 不會(huì)單獨(dú)存在:

try :

print whisper()

except NameError, e :

print e

#輸出 : "name 'whisper' is not defined"*

函數(shù)引用

從以上兩個(gè)例子我們可以得出,函數(shù)既然作為一個(gè)對(duì)象,因此:

1. 其可以被賦給其他變量

2. 其可以被定義在另外一個(gè)函數(shù)內(nèi)

這也就是說,函數(shù)可以返回一個(gè)函數(shù),看下面的例子:

def getTalk(type="shout") :

# 我們定義另外一個(gè)函數(shù)

def shout(word="yes") :

return word.capitalize()+" !"

def whisper(word="yes") :

return word.lower()+"...";

# 然后我們返回其中一個(gè)

if type == "shout" :

# 我們沒有使用(),因?yàn)槲覀儾皇窃谡{(diào)用該函數(shù)

# 我們是在返回該函數(shù)

return shout

else :

return whisper

# 然后怎么使用呢 ?

# 把該函數(shù)賦予某個(gè)變量

talk = getTalk()

# 這里你可以看到talk其實(shí)是一個(gè)函數(shù)對(duì)象:

print talk

#輸出 : function shout at 0xb7ea817c

# 該對(duì)象由函數(shù)返回的其中一個(gè)對(duì)象:

print talk()

# 或者你可以直接如下調(diào)用 :

print getTalk("whisper")()

#輸出 : yes...

還有,既然可以返回一個(gè)函數(shù),我們可以把它作為參數(shù)傳遞給函數(shù):

def doSomethingBefore(func) :

print "I do something before then I call the function you gave me"

print func()

doSomethingBefore(scream)

#輸出 :

#I do something before then I call the function you gave me

#Yes !

這里你已經(jīng)足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前后執(zhí)行代碼而無須改變函數(shù)本身內(nèi)容。

手工裝飾

那么如何進(jìn)行手動(dòng)裝飾呢?

# 裝飾器是一個(gè)函數(shù),而其參數(shù)為另外一個(gè)函數(shù)

def my_shiny_new_decorator(a_function_to_decorate) :

# 在內(nèi)部定義了另外一個(gè)函數(shù):一個(gè)封裝器。

# 這個(gè)函數(shù)將原始函數(shù)進(jìn)行封裝,所以你可以在它之前或者之后執(zhí)行一些代碼

def the_wrapper_around_the_original_function() :

# 放一些你希望在真正函數(shù)執(zhí)行前的一些代碼

print "Before the function runs"

# 執(zhí)行原始函數(shù)

a_function_to_decorate()

# 放一些你希望在原始函數(shù)執(zhí)行后的一些代碼

print "After the function runs"

#在此刻,"a_function_to_decrorate"還沒有被執(zhí)行,我們返回了創(chuàng)建的封裝函數(shù)

#封裝器包含了函數(shù)以及其前后執(zhí)行的代碼,其已經(jīng)準(zhǔn)備完畢

return the_wrapper_around_the_original_function

# 現(xiàn)在想象下,你創(chuàng)建了一個(gè)你永遠(yuǎn)也不遠(yuǎn)再次接觸的函數(shù)

def a_stand_alone_function() :

print "I am a stand alone function, don't you dare modify me"

a_stand_alone_function()

#輸出: I am a stand alone function, don't you dare modify me

# 好了,你可以封裝它實(shí)現(xiàn)行為的擴(kuò)展。可以簡(jiǎn)單的把它丟給裝飾器

# 裝飾器將動(dòng)態(tài)地把它和你要的代碼封裝起來,并且返回一個(gè)新的可用的函數(shù)。

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)

a_stand_alone_function_decorated()

#輸出 :

#Before the function runs

#I am a stand alone function, don't you dare modify me

#After the function runs

現(xiàn)在你也許要求當(dāng)每次調(diào)用a_stand_alone_function時(shí),實(shí)際調(diào)用卻是a_stand_alone_function_decorated。實(shí)現(xiàn)也很簡(jiǎn)單,可以用my_shiny_new_decorator來給a_stand_alone_function重新賦值。

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)

a_stand_alone_function()

#輸出 :

#Before the function runs

#I am a stand alone function, don't you dare modify me

#After the function runs

# And guess what, that's EXACTLY what decorators do !

裝飾器揭秘

前面的例子,我們可以使用裝飾器的語(yǔ)法:

@my_shiny_new_decorator

def another_stand_alone_function() :

print "Leave me alone"

another_stand_alone_function()

#輸出 :

#Before the function runs

#Leave me alone

#After the function runs

當(dāng)然你也可以累積裝飾:

def bread(func) :

def wrapper() :

print "/''''''\"

func()

print "\______/"

return wrapper

def ingredients(func) :

def wrapper() :

print "#tomatoes#"

func()

print "~salad~"

return wrapper

def sandwich(food="--ham--") :

print food

sandwich()

#輸出 : --ham--

sandwich = bread(ingredients(sandwich))

sandwich()

#outputs :

#/''''''\

# #tomatoes#

# --ham--

# ~salad~

#\______/

使用python裝飾器語(yǔ)法:

@bread

@ingredients

def sandwich(food="--ham--") :

print food

sandwich()

#輸出 :

#/''''''\

# #tomatoes#

# --ham--

# ~salad~

#\______/

當(dāng)前名稱:關(guān)于python在函數(shù)里計(jì)時(shí)的信息
文章位置:http://muchs.cn/article30/dooccso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、搜索引擎優(yōu)化、網(wǎng)站制作、網(wǎng)頁(yè)設(shè)計(jì)公司、網(wǎng)站維護(hù)、小程序開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)