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