摘要:我們以測量函數(shù)運行時間為例來講一講裝飾器的運行原理。三更加通用的裝飾器前面兩部分講了裝飾器的原理,這一部分就講講要寫出一個通用的裝飾器需要注意的問題。首先就是參數(shù)的問題,裝飾器返回的函數(shù)不是原來的函數(shù),函數(shù)的簽名也就和原來的函數(shù)簽名不一樣。
一、最簡單的裝飾器
裝飾器是python中很基礎(chǔ)也很實用的一個特性。通過裝飾器我們可以很方便地為一些函數(shù)添加相同的功能。我們以測量函數(shù)運行時間為例來講一講python裝飾器的運行原理。
1、使用裝飾器打印函數(shù)運行時間通常我們使用 time 庫來獲取函數(shù)的運行時間。
import time # 函數(shù)運行 2秒 def func(): time.sleep(2) start_time = time.time() func() end_time = time.time() print("函數(shù)運行時間為:%.2fs" % (end_time - start_time))
so easy,現(xiàn)在我們來思考一下:如果我們有 100個函數(shù),怎么打印這100個函數(shù)的運行時間呢?
總不能按照上面的寫法來100次吧,這絕不是一個程序猿應(yīng)該做的事,這時候裝飾器就可以排上用場了。
我們先看代碼,然后再慢慢講其中的原理。
import time def timeit(func): def result(): start_time = time.time() func() end_time = time.time() print("函數(shù)運行時間為:%.2fs" % (end_time - start_time)) return result @timeit def func_0(): time.sleep(2) # 省略98個 @timeit def func_99(): time.sleep(2) # 接下來直接調(diào)用這100個函數(shù)即可 func_0() # ...
使用了 timeit 裝飾器的函數(shù)和沒使用裝飾器的原始方法效果一樣,不過這只是裝飾器最簡單的一個應(yīng)用,我們下面來看看其中的原理。
2、timeit的運行原理首先,大家需要知道在python中的函數(shù)也是對象,是對象就可以作為參數(shù)傳遞,這是裝飾器實現(xiàn)的基礎(chǔ)。
接下來我們來看看裝飾器 timeit,不難看出 timeit 是一個函數(shù)。準(zhǔn)確地說 timeit 是一個接受一個函數(shù)為參數(shù)并且返回一個函數(shù)的函數(shù)。
聽著挺拗口的,別急,看我細(xì)細(xì)道來。
timeit 是一個函數(shù),它接受一個參數(shù),這個參數(shù)必須是函數(shù)(或者說可調(diào)用對象),并且 timeit 的返回結(jié)果一定是一個函數(shù)。
看到這里大家應(yīng)該對裝飾器有一點了解了,原來裝飾器就是接受一個函數(shù)為參數(shù)返回另一個函數(shù)的函數(shù)。
現(xiàn)在我們就可以解釋 timeit 的運行原理了,看下面的代碼
# 代碼塊1 @timeit def func_0(): time.sleep(2) # 代碼塊2 def func_0(): time.sleep(2) func_0 = timeit(func_0)
這里的代碼塊1和代碼塊2完全等價,注意完全兩個字,這說明這兩段代碼的效果一模一樣。
事實上代碼塊1就是代碼塊2的簡寫形式,因為代碼塊2的寫法挺麻煩的,所以python的設(shè)計者加了一些語法糖,就有了代碼塊1的寫法。
了解了這些我們再來看看 timeit 內(nèi)部
def timeit(func): def result(): start_time = time.time() func() end_time = time.time() print("函數(shù)運行時間為:%.2fs" % (end_time - start_time)) return result
在 timeit里面我們定義了一個函數(shù) result ,函數(shù) result 里面的內(nèi)容我們很熟悉,就是打印函數(shù) func 的運行時間。這里的 func 就是我們要裝飾的函數(shù)。
最后我們將函數(shù) result 返回,我們再看到代碼塊2,返回的 result 函數(shù)替換了我們要裝飾的函數(shù) func_0,這時當(dāng)我們再調(diào)用函數(shù) func_0 時其實就是在調(diào)用函數(shù) result。
3、什么是閉包我們看一段代碼:
def outer(): a = 0 def inner(): print(a) return inner func = outer() func()
上面的函數(shù)和裝飾器很像,以前學(xué)C語言的可能會疑惑為什么變量a在函數(shù) outer 調(diào)用完成后仍然能夠被訪問。
這和python中變量的回收機制有關(guān),python根據(jù)變量的引用計數(shù)來判斷是變量是否需要回收。
當(dāng)一個對象的引用被創(chuàng)建或復(fù)制時,對象的引用計數(shù)加1;當(dāng)一個對象的引用被銷毀時,對象的引用計數(shù)減1,如果對象的引用計數(shù)減少為0,將對象的所占用的內(nèi)存釋放。
上面的例子中因為對象a的引用一直沒有被銷毀引用計數(shù)不為0,所以在函數(shù) inner 里一直可以訪問對象a的值。
而且我們發(fā)現(xiàn)在調(diào)用過函數(shù) outer 后只有函數(shù) inner 可以訪問對象a。
這就相當(dāng)于為函數(shù) inner添加了一個私有的命名空間,而且只有函數(shù) inner 可以訪問這個命名空間,這就是python中的閉包。
裝飾器就是利用了閉包的原理,所以我們說裝飾器就是是閉包也是完全沒有問題的。
二、帶參數(shù)的裝飾器我們可能看到過這樣的裝飾器
@decorator("arg","arg2") def func(): # do something pass
在一些代碼中我們發(fā)現(xiàn)有些裝飾器是有參數(shù)的,這樣可以帶參數(shù)的裝飾器是怎么實現(xiàn)的呢?下面我就和大家講一講帶參數(shù)的裝飾器是怎么實現(xiàn)的。
在了解了裝飾器的原理之后我們知道裝飾器其實就是一個返回一個函數(shù)的函數(shù)。
于是我們就想:既然有返回函數(shù)的函數(shù),那有沒有返回裝飾器的函數(shù)呢?當(dāng)然是有的!
事實上,上面提到的帶參數(shù)的裝飾器就是一個返回裝飾器的函數(shù)。
其中的原理也很簡單,圓括號表示函數(shù)調(diào)用,而我們的程序都是從右到左執(zhí)行的,所以在程序運行的時候 @ 后面的應(yīng)該是 decorator 返回的裝飾器。
說完原理,我們再來看看帶參數(shù)的裝飾器應(yīng)該怎么寫,先上代碼。
import time def timeit(out="函數(shù)運行時間為:%.2fs"): def decorator(func): def result(): start_time = time.time() func() end_time = time.time() print(out % (end_time - start_time)) return result return decorator @timeit("函數(shù)運行時間為:%.2fs --來自帶參數(shù)的裝飾器") def func(): time.sleep(2) func() # => 函數(shù)運行時間為:2.00s --來自帶參數(shù)的裝飾器
我們在原來打印函數(shù)運行時間的裝飾器上添加了自定義打印語句的功能,在使用的時候和下面的代碼等價。
func = timeit("函數(shù)運行時間為:%.2fs --來自帶參數(shù)的裝飾器")(func)
帶參數(shù)的裝飾器也是利用了閉包將參數(shù)綁定到返回的函數(shù)上。
三、更加通用的裝飾器前面兩部分講了裝飾器的原理,這一部分就講講要寫出一個通用的裝飾器需要注意的問題。
首先就是參數(shù)的問題,裝飾器返回的函數(shù)不是原來的函數(shù),函數(shù)的簽名也就和原來的函數(shù)簽名不一樣。
當(dāng)我們還是按照來的方式去調(diào)用函數(shù)就有可能會出問題,我們通過一個例子來看一下。
def decorator(func): def result(a): print(a) func() return result @decorator def func(a,b,c): print(a,b,c) func(1, 2, 3) # => TypeError: result() takes 1 positional argument but 3 were given
這里報錯是因為裝飾器返回的函數(shù)只接受一個參數(shù),我們還按照調(diào)用 func 的方式來調(diào)用 result當(dāng)然會報錯。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/44065.html
摘要:下面我們一起拋去無關(guān)概念,簡單地理解下的裝飾器。用函數(shù)實現(xiàn)裝飾器裝飾器要求入?yún)⑹呛瘮?shù)對象,返回值是函數(shù)對象,嵌套函數(shù)完全能勝任。為了對調(diào)用方透明,裝飾器返回的對象要偽裝成被裝飾的函數(shù)。 來源:http://www.lightxue.com/under... ???????Python有大量強大又貼心的特性,如果要列個最受歡迎排行榜,那么裝飾器絕對會在其中。???????剛接觸裝飾器,會...
摘要:概括的講,裝飾器的作用就是為已經(jīng)存在的函數(shù)或?qū)ο筇砑宇~外的功能。在理解這些裝飾器之前,最好對函數(shù)的閉包和裝飾器的接口約定有一定了解。是一個非常簡單的裝飾器加強包。 Python中的裝飾器是你進入Python大門的一道坎,不管你跨不跨過去它都在那里。 為什么需要裝飾器 我們假設(shè)你的程序?qū)崿F(xiàn)了say_hello()和say_goodbye()兩個函數(shù)。 def say_hello(): ...
摘要:今天就結(jié)合最近的世界杯帶大家理解下裝飾器。而德國是上屆的冠軍,又是這屆奪冠熱門。裝飾器的存在是為了適用兩個場景,一個是增強被裝飾函數(shù)的行為,另一個是代碼重用。在利用語法糖,簡化賦值操作。行為良好的裝飾器可以重用,以減少代碼量。 Python 裝飾器是在面試過程高頻被問到的問題,裝飾器也是一個非常好用的特性,熟練掌握裝飾器會讓你的編程思路更加寬廣,程序也更加 pythonic。 show...
摘要:裝飾器的使用符合了面向?qū)ο缶幊痰拈_放封閉原則。三簡單的裝飾器基于上面的函數(shù)執(zhí)行時間的需求,我們就手寫一個簡單的裝飾器進行實現(xiàn)。函數(shù)體就是要實現(xiàn)裝飾器的內(nèi)容。類裝飾器的實現(xiàn)是調(diào)用了類里面的函數(shù)。類裝飾器的寫法比我們裝飾器函數(shù)的寫法更加簡單。 目錄 前言 一、什么是裝飾器 二、為什么要用裝飾器 ...
摘要:裝飾器是可調(diào)用的對象,其參數(shù)是另一個函數(shù)被裝飾的函數(shù)。第二大特性是,裝飾器在加載模塊時立即執(zhí)行。另一個常見的裝飾器是,它的作用是協(xié)助構(gòu)建行為良好的裝飾器。 裝飾器是可調(diào)用的對象,其參數(shù)是另一個函數(shù)(被裝飾的函數(shù))。 裝飾器基礎(chǔ)知識 首先看一下這段代碼 def deco(fn): print I am %s! % fn.__name__ @deco def func(): ...
閱讀 1472·2021-11-24 09:39
閱讀 1788·2021-11-22 15:25
閱讀 3740·2021-11-19 09:40
閱讀 3299·2021-09-22 15:31
閱讀 1299·2021-07-29 13:49
閱讀 1209·2019-08-26 11:59
閱讀 1321·2019-08-26 11:39
閱讀 934·2019-08-26 11:00