就是告訴你有錯誤。你for之前那個clock()是什么?哪里來的?
10年積累的成都網站設計、網站建設經驗,可以快速應對客戶對網站的新想法和需求。提供各種問題對應的解決方案。讓選擇我們的客戶得到更好、更有力的網絡服務。我雖然不認識你,你也不認識我。但先網站設計后付款的網站建設流程,更有桃源免費網站建設讓你可以放心的選擇與我們合作。
就算你把它刪除掉,最后那個print里用的clock又怎么辦?
如果你想使用time.clock()做計時:
你需要在文件開始引入這個包里的clock:
from?time?import?clock
然后,在開始計時的地方保存clock()的值:
t?=?clock()
在結束計時的地方計算
clock()?-?t
即為用時。
如果要使用time.perf_counter()做計時:
修改相應的import語句為
from?time?import?perf_counter?as?clock
其它代碼不需要修改即可使用。
但有一點需要注意的是,perf_counter自第一次引用后,它就開始計時,之后無論調用多少次,它都是返回當前時間到開始計時的時間差,這會產生一個問題:
如果在其它模塊中導入了它到全局范圍,那么,所有模塊中使用此函數的將都使用同一個計時器,這會讓使用它得到的時長超出期望。
一般情況下,這是用來測試一段程序的用時的,應當避免使用影響到其它環(huán)境或受其它環(huán)境影響的方式,所以建議你使用第一種方式去處理這個問題
Julia 與 Python
的比較
我是否應丟棄 Python 和其他語言,使用 Julia 執(zhí)行技術計算?在看到 上的基準測試后,人們一定會這么想。Python
和其他高級語言在速度上遠遠有些落后。但是,我想到的第一個問題有所不同:Julia 團隊能否以最適合 Python 的方式編寫 Python 基準測試?
我對這種跨語言比較的觀點是,應該根據要執(zhí)行的任務來定義基準測試,然后由語言專家編寫執(zhí)行這些任務的最佳代碼。如果代碼全由一個語言團隊編寫,則存在其他語言未得到最佳使用的風險。
Julia 團隊有一件事做得對,那就是他們將他們使用的代碼發(fā)布到了 github 上。具體地講,Python 代碼可在此處找到。
第一眼看到該代碼,就可以證實我所害怕的偏見。該代碼是以 C 風格編寫的,在數組和列表上大量使用了循環(huán)。這不是使用 Python 的最佳方式。
我不會責怪 Julia 團隊,因為我很內疚自己也有同樣的偏見。但我受到了殘酷的教訓:付出任何代價都要避免數組或列表上的循環(huán),因為它們確實會拖慢 Python
中的速度,請參閱 Python 不是 C。
考慮到對 C 風格的這種偏見,一個有趣的問題(至少對我而言)是,我們能否改進這些基準測試,更好地使用 Python 及其工具?
在我給出答案之前,我想說我絕不會試圖貶低 Julia。在進一步開發(fā)和改進后,Julia 無疑是一種值得關注的語言。我只是想分析 Python
方面的事情。實際上,我正在以此為借口來探索各種可用于讓代碼更快運行的 Python 工具。
在下面的內容中,我使用 Docker 鏡像在 Jupyter Notebook 中使用 Python 3.4.3,其中已安裝了所有的 Python 科學工具組合。我還會通過
Windows 機器上的 Python 2.7.10,使用 Anaconda 來運行代碼。計時是對 Python 3.4.3 執(zhí)行的。包含下面的所有基準測試的完整代碼的 Notebook 可在此處找到。
鑒于各種社交媒體上的評論,我添加了這樣一句話:我沒有在這里使用 Python 的替代性實現。我沒有編寫任何 C
代碼:如果您不信,可試試尋找分號。本文中使用的所有工具都是 Anaconda 或其他發(fā)行版中提供的標準的 Cython 實現。下面的所有代碼都在單個 Notebook中運行。
我嘗試過使用來自 github 的 Julia 微性能文件,但不能使用 Julia 0.4.2 原封不動地運行它。我必須編輯它并將 @timeit 替換為
@time,它才能運行。在對它們計時之前,我還必須添加對計時函數的調用,否則編譯時間也將包含在內。我使用的文件位于此處。我在用于運行 Python 的同一個機器上使用 Julia 命令行接口運行它。
回頁首
計時代碼
Julia 團隊使用的第一項基準測試是 Fibonacci 函數的一段簡單編碼。
def fib(n):
if n2:
return n
return fib(n-1)+fib(n-2)
此函數的值隨 n 的增加而快速增加,例如:
fib(100) = 354224848179261915075
可以注意到,Python 任意精度 (arbitrary precision) 很方便。在 C 等語言中編寫相同的函數需要花一些編碼工作來避免整數溢出。在 Julia
中,需要使用 BigInt 類型。
所有 Julia 基準測試都與運行時間有關。這是 Julia 中使用和不使用 BigInt 的計時:
0.000080 seconds (149 allocations:10.167 KB)
0.012717 seconds (262.69 k allocations:4.342 MB)
在 Python Notebook 中獲得運行時間的一種方式是使用神奇的 %timeit。例如,在一個新單元中鍵入:
%timeit fib(20)
執(zhí)行它會獲得輸出:
100 loops, best of 3:3.33 ms per loop
這意味著計時器執(zhí)行了以下操作:
運行 fib(20) 100 次,存儲總運行時間
運行 fib(20) 100 次,存儲總運行時間
運行 fib(20) 100 次,存儲總運行時間
從 3 次運行中獲取最小的運行時間,將它除以 100,然后輸出結果,該結果就是 fib(20) 的最佳運行時間
這些循環(huán)的大小(100 次和 3 次)會由計時器自動調整??赡軙鶕挥嫊r的代碼的運行速度來更改循環(huán)大小。
Python 計時與使用了 BigInt 時的 Julia 計時相比出色得多:3 毫秒與 12 毫秒。在使用任意精度時,Python 的速度是 Julia 的 4
倍。
但是,Python 比 Julia 默認的 64 位整數要慢。我們看看如何在 Python 中強制使用 64 位整數。
回頁首
使用 Cython 編譯
一種編譯方式是使用 Cython 編譯器。這個編譯器是使用 Python
編寫的。它可以通過以下命令安裝:
pip install Cython
如果使用 Anaconda,安裝會有所不同。因為安裝有點復雜,所以我編寫了一篇相關的博客文章:將 Cython For Anaconda 安裝在 Windows 上
安裝后,我們使用神奇的 %load_ext 將 Cython 加載到 Notebook 中:
%load_ext Cython
然后就可以在我們的 Notebook 中編譯代碼。我們只需要將想要編譯的代碼放在一個單元中,包括所需的導入語句,使用神奇的 %%cython 啟動該單元:
%%cython
def fib_cython(n):
if n2:
return n
return fib_cython(n-1)+fib_cython(n-2)
執(zhí)行該單元會無縫地編譯這段代碼。我們?yōu)樵摵瘮凳褂靡粋€稍微不同的名稱,以反映出它是使用 Cython
編譯的。當然,一般不需要這么做。我們可以將之前的函數替換為相同名稱的已編譯函數。
對它計時會得到:
1000 loops, best of 3:1.22 ms per loop
哇,幾乎比最初的 Python 代碼快 3 倍!我們現在比使用 BigInt 的 Julia 快 100 倍。
我們還可以嘗試靜態(tài)類型。使用關鍵字 cpdef 而不是 def 來聲明該函數。它使我們能夠使用相應的 C 類型來鍵入函數的參數。我們的代碼變成了:
%%cython
cpdef long fib_cython_type(long n):
if n2:
return n
return fib_cython_type(n-1)+fib_cython_type(n-2)
執(zhí)行該單元后,對它計時會得到:
10000 loops, best of 3:36 μs per loop
太棒了,我們現在只花費了 36 微秒,比最初的基準測試快約 100 倍!這與 Julia 所花的 80 毫秒相比更出色。
有人可能會說,靜態(tài)類型違背了 Python
的用途。一般來講,我比較同意這種說法,我們稍后將查看一種在不犧牲性能的情況下避免這種情形的方法。但我并不認為這是一個問題。Fibonacci
函數必須使用整數來調用。我們在靜態(tài)類型中失去的是 Python 所提供的任意精度。對于 Fibonacci,使用 C 類型 long
會限制輸入參數的大小,因為太大的參數會導致整數溢出。
請注意,Julia 計算也是使用 64 位整數執(zhí)行的,因此將我們的靜態(tài)類型版本與 Julia 的對比是公平的。
回頁首
緩存計算
我們在保留 Python 任意精度的情況下能做得更好。fib 函數重復執(zhí)行同一種計算許多次。例如,fib(20) 將調用 fib(19) 和
fib(18)。fib(19) 將調用 fib(18) 和 fib(17)。結果 fib(18) 被調用了兩次。簡單分析表明,fib(17) 將被調用 3
次,fib(16) 將被調用 5 次,等等。
在 Python 3 中,我們可以使用 functools 標準庫來避免這些重復的計算。
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)
對此函數計時會得到:
1000000 loops, best of 3:910 ns per loop
速度又增加了 40 倍,比最初的 Python 代碼快約 3,600 倍!考慮到我們僅向遞歸函數添加了一條注釋,此結果非常令人難忘。
Python 2.7 中沒有提供這種自動緩存。我們需要顯式地轉換代碼,才能避免這種情況下的重復計算。
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
請注意,此代碼使用了 Python 同時分配兩個局部變量的能力。對它計時會得到:
1000000 loops, best of 3:1.77 μs per loop
我們又快了 20 倍!讓我們在使用和不使用靜態(tài)類型的情況下編譯我們的函數。請注意,我們使用了 cdef 關鍵字來鍵入局部變量。
%%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
我們可在一個單元中對兩個版本計時:
%timeit fib_seq_cython(20)
%timeit fib_seq_cython_type(20)
結果為:
1000000 loops, best of 3:953 ns per loop
10000000 loops, best of 3:51.9 ns per loop
靜態(tài)類型代碼現在花費的時間為 51.9 納秒,比最初的基準測試快約 60,000(六萬)倍。
如果我們想計算任意輸入的 Fibonacci 數,我們應堅持使用無類型版本,該版本的運行速度快 3,500 倍。還不錯,對吧?
回頁首
使用 Numba 編譯
讓我們使用另一個名為 Numba 的工具。它是針對部分 Python 版本的一個即時
(jit) 編譯器。它不是對所有 Python 版本都適用,但在適用的情況下,它會帶來奇跡。
安裝它可能很麻煩。推薦使用像 Anaconda 這樣的 Python 發(fā)行版或一個已安裝了 Numba 的 Docker 鏡像。完成安裝后,我們導入它的 jit 編譯器:
from numba import jit
它的使用非常簡單。我們僅需要向想要編譯的函數添加一點修飾。我們的代碼變成了:
@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
對它計時會得到:
1000000 loops, best of 3:225 ns per loop
比無類型的 Cython 代碼更快,比最初的 Python 代碼快約 16,000 倍!
回頁首
使用 Numpy
我們現在來看看第二項基準測試。它是快速排序算法的實現。Julia 團隊使用了以下 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
我將他們的基準測試代碼包裝在一個函數中:
import random
def benchmark_qsort():
lst = [ random.random() for i in range(1,5000) ]
qsort_kernel(lst, 0, len(lst)-1)
對它計時會得到:
100 loops, best of 3:18.3 ms per loop
上述代碼與 C 代碼非常相似。Cython 應該能很好地處理它。除了使用 Cython 和靜態(tài)類型之外,讓我們使用 Numpy
數組代替列表。在數組大小較大時,比如數千個或更多元素,Numpy 數組確實比
Python 列表更快。
安裝 Numpy 可能會花一些時間,推薦使用 Anaconda 或一個已安裝了 Python 科學工具組合的 Docker 鏡像。
在使用 Cython 時,需要將 Numpy 導入到應用了 Cython 的單元中。在使用 C 類型時,還必須使用 cimport 將它作為 C 模塊導入。Numpy
數組使用一種表示數組元素類型和數組維數(一維、二維等)的特殊語法來聲明。
%%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)
對 benchmark_qsort_numpy_cython() 函數計時會得到:
1000 loops, best of 3:1.32 ms per loop
我們比最初的基準測試快了約 15 倍,但這仍然不是使用 Python 的最佳方法。最佳方法是使用 Numpy 內置的 sort()
函數。它的默認行為是使用快速排序算法。對此代碼計時:
def benchmark_sort_numpy():
lst = np.random.rand(5000)
np.sort(lst)
會得到:
1000 loops, best of 3:350 μs per loop
我們現在比最初的基準測試快 52 倍!Julia 在該基準測試上花費了 419 微秒,因此編譯的 Python 快 20%。
我知道,一些讀者會說我不會進行同類比較。我不同意。請記住,我們現在的任務是使用主機語言以最佳的方式排序輸入數組。在這種情況下,最佳方法是使用一個內置的函數。
time.sleep在python3.11中替換為python。
INTRO:眾所周知,time.sleep的準確率取決于操作系統(tǒng)和計算負載。 Windows 中的準確性非常差。
類似于 /questions/17499837一個方法可以使用 time.clock 實現忙等待方法作為 time.sleep 的替代方法.這種方法會造成不必要的負載,影響系統(tǒng)中的其他模 block 。這在進行模擬時是不可取的。
減少花在忙等待上的時間,而不是依賴 time.sleep , 一個類使用方法 select.select并利用超時屬性。
使用time.time來統(tǒng)計函數的執(zhí)行時間,程序只會執(zhí)行一次,存在很大的隨機因素。
timtit包就可以重復執(zhí)行函數多次,然后將多次執(zhí)行結果取平均值。相比起來更優(yōu)。
然而程序執(zhí)行時間很大程度還受計算機性能的影響,衡量程序好壞更靠譜的手段是計算時間復雜度。
理解Python中的裝飾器
@makebold
@makeitalic
def say():
return "Hello"
打印出如下的輸出:
biHelloi/b
你會怎么做?最后給出的答案是:
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
現在我們來看看如何從一些最基礎的方式來理解Python的裝飾器。英文討論參考Here。
裝飾器是一個很著名的設計模式,經常被用于有切面需求的場景,較為經典的有插入日志、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
1.1. 需求是怎么來的?
裝飾器的定義很是抽象,我們來看一個小例子。
def foo():
print 'in foo()'
foo()
這是一個很無聊的函數沒錯。但是突然有一個更無聊的人,我們稱呼他為B君,說我想看看執(zhí)行這個函數用了多長時間,好吧,那么我們可以這樣做:
import time
def foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'used:', end - start
foo()
很好,功能看起來無懈可擊??墒堑疤鄣腂君此刻突然不想看這個函數了,他對另一個叫foo2的函數產生了更濃厚的興趣。
怎么辦呢?如果把以上新增加的代碼復制到foo2里,這就犯了大忌了~復制什么的難道不是最討厭了么!而且,如果B君繼續(xù)看了其他的函數呢?
1.2. 以不變應萬變,是變也
還記得嗎,函數在Python中是一等公民,那么我們可以考慮重新定義一個函數timeit,將foo的引用傳遞給他,然后在timeit中調用foo并進行計時,這樣,我們就達到了不改動foo定義的目的,而且,不論B君看了多少個函數,我們都不用去修改函數定義了!
import time
def foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
timeit(foo)
看起來邏輯上并沒有問題,一切都很美好并且運作正常!……等等,我們似乎修改了調用部分的代碼。原本我們是這樣調用的:foo(),修改以后變成了:timeit(foo)。這樣的話,如果foo在N處都被調用了,你就不得不去修改這N處的代碼?;蛘吒鼧O端的,考慮其中某處調用的代碼無法修改這個情況,比如:這個函數是你交給別人使用的。
1.3. 最大限度地少改動!
既然如此,我們就來想想辦法不修改調用的代碼;如果不修改調用代碼,也就意味著調用foo()需要產生調用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個參數……想辦法把參數統(tǒng)一吧!如果timeit(foo)不是直接產生調用效果,而是返回一個與foo參數列表一致的函數的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然后,調用foo()的代碼完全不用修改!
#-*- coding: UTF-8 -*-
import time
def foo():
print 'in foo()'
# 定義一個計時器,傳入一個,并返回另一個附加了計時功能的方法
def timeit(func):
# 定義一個內嵌的包裝函數,給傳入的函數加上計時功能的包裝
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
# 將包裝后的函數返回
return wrapper
foo = timeit(foo)
foo()
這樣,一個簡易的計時器就做好了!我們只需要在定義foo以后調用foo之前,加上foo = timeit(foo),就可以達到計時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個例子中,函數進入和退出時需要計時,這被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統(tǒng)編程習慣的從上往下執(zhí)行方式相比較而言,像是在函數執(zhí)行的流程中橫向地插入了一段邏輯。在特定的業(yè)務領域里,能減少大量重復代碼。面向切面編程還有相當多的術語,這里就不多做介紹,感興趣的話可以去找找相關的資料。
這個例子僅用于演示,并沒有考慮foo帶有參數和有返回值的情況,完善它的重任就交給你了 :)
上面這段代碼看起來似乎已經不能再精簡了,Python于是提供了一個語法糖來降低字符輸入量。
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()
重點關注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價,千萬不要以為@有另外的魔力。除了字符輸入少了一些,還有一個額外的好處:這樣看上去更有裝飾器的感覺。
-------------------
要理解python的裝飾器,我們首先必須明白在Python中函數也是被視為對象。這一點很重要。先看一個例子:
def shout(word="yes") :
return word.capitalize()+" !"
print shout()
# 輸出 : 'Yes !'
# 作為一個對象,你可以把函數賦給任何其他對象變量
scream = shout
# 注意我們沒有使用圓括號,因為我們不是在調用函數
# 我們把函數shout賦給scream,也就是說你可以通過scream調用shout
print scream()
# 輸出 : 'Yes !'
# 還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數
del shout
try :
print shout()
except NameError, e :
print e
#輸出 : "name 'shout' is not defined"
print scream()
# 輸出 : 'Yes !'
我們暫且把這個話題放旁邊,我們先看看python另外一個很有意思的屬性:可以在函數中定義函數:
def talk() :
# 你可以在talk中定義另外一個函數
def whisper(word="yes") :
return word.lower()+"...";
# ... 并且立馬使用它
print whisper()
# 你每次調用'talk',定義在talk里面的whisper同樣也會被調用
talk()
# 輸出 :
# yes...
# 但是"whisper" 不會單獨存在:
try :
print whisper()
except NameError, e :
print e
#輸出 : "name 'whisper' is not defined"*
函數引用
從以上兩個例子我們可以得出,函數既然作為一個對象,因此:
1. 其可以被賦給其他變量
2. 其可以被定義在另外一個函數內
這也就是說,函數可以返回一個函數,看下面的例子:
def getTalk(type="shout") :
# 我們定義另外一個函數
def shout(word="yes") :
return word.capitalize()+" !"
def whisper(word="yes") :
return word.lower()+"...";
# 然后我們返回其中一個
if type == "shout" :
# 我們沒有使用(),因為我們不是在調用該函數
# 我們是在返回該函數
return shout
else :
return whisper
# 然后怎么使用呢 ?
# 把該函數賦予某個變量
talk = getTalk()
# 這里你可以看到talk其實是一個函數對象:
print talk
#輸出 : function shout at 0xb7ea817c
# 該對象由函數返回的其中一個對象:
print talk()
# 或者你可以直接如下調用 :
print getTalk("whisper")()
#輸出 : yes...
還有,既然可以返回一個函數,我們可以把它作為參數傳遞給函數:
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 !
這里你已經足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前后執(zhí)行代碼而無須改變函數本身內容。
手工裝飾
那么如何進行手動裝飾呢?
# 裝飾器是一個函數,而其參數為另外一個函數
def my_shiny_new_decorator(a_function_to_decorate) :
# 在內部定義了另外一個函數:一個封裝器。
# 這個函數將原始函數進行封裝,所以你可以在它之前或者之后執(zhí)行一些代碼
def the_wrapper_around_the_original_function() :
# 放一些你希望在真正函數執(zhí)行前的一些代碼
print "Before the function runs"
# 執(zhí)行原始函數
a_function_to_decorate()
# 放一些你希望在原始函數執(zhí)行后的一些代碼
print "After the function runs"
#在此刻,"a_function_to_decrorate"還沒有被執(zhí)行,我們返回了創(chuàng)建的封裝函數
#封裝器包含了函數以及其前后執(zhí)行的代碼,其已經準備完畢
return the_wrapper_around_the_original_function
# 現在想象下,你創(chuàng)建了一個你永遠也不遠再次接觸的函數
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
# 好了,你可以封裝它實現行為的擴展??梢院唵蔚陌阉鼇G給裝飾器
# 裝飾器將動態(tài)地把它和你要的代碼封裝起來,并且返回一個新的可用的函數。
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
現在你也許要求當每次調用a_stand_alone_function時,實際調用卻是a_stand_alone_function_decorated。實現也很簡單,可以用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 !
裝飾器揭秘
前面的例子,我們可以使用裝飾器的語法:
@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
當然你也可以累積裝飾:
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裝飾器語法:
@bread
@ingredients
def sandwich(food="--ham--") :
print food
sandwich()
#輸出 :
#/''''''\
# #tomatoes#
# --ham--
# ~salad~
#\______/