Python 裝飾器詳解(上)

Python 裝飾器詳解(上)

轉自:https://blog.csdn.net/qq_27825451/article/details/84396970,博主僅對其中 demo 實現中不適合python3 版本的語法進行修改,并微調了排版,本轉載博客全部例程博主均已親測可行。

Python 3.8.5

ubuntu 18.04

一、先從一種情況看起

1. 裝飾器decorator的由來

裝飾器的定義很是抽象,我們來看一個小例子。先定義一個簡單的函數:

def myfunc():print('我是函數myfunc')myfunc()  #調用函數

然后呢,我想看看這個函數執行這個函數用了多長時間,好吧,那么我們可以這樣做:

import time
def myfunc():start = time.time()print('我是函數myfunc')end = time.time()print(f'函數所花費的時間為 :{end - start}')myfunc()  #函數調用

我們現在已經達到了我們的目的。但是如果是我們還想繼續給另外的一些函數也實現同樣的功能。那我們是不是給每個函數都添加這么幾句話呢?當然可以,但是不高效,而且很麻煩。如果有某一種方式可以一次性解決所有的問題,那自然最好不過了,于是“裝飾器”就應運而生。

在上面的例子中,函數本身的功能只是打印一句話而已,但是經過改造后的函數不僅要能夠打印這一句話,還要能夠顯示函數執行所花費的時間,這相當于我要給這個函數添加額外的功能,注意這個關鍵字,其實“裝飾器”就是專門給函數添加額外的功能的

2. 添加額外功能的簡單實現——非“裝飾器”實現

還記得嗎,函數在Python中是一等公民,那么我們可以考慮重新定義一個函數timeit,將myfunc的引用傳遞給他,然后在timeit中調用myfunc并進行計時,這樣,我們就達到了不改動myfunc定義但是又添加了額外功能的目的,代碼如下:

import timedef myfunc():print("我是函數myfunc")def timeit(function):start = time.time()function()end =time.time()print(f'函數執行所花費的時間為:{end-start}')timeit(myfunc)

運行結果為:

我是函數myfunc
函數執行所花費的時間為:1.9311904907226562e-05

上面的代碼看起來邏輯上并沒有問題,也達到了我們所要實現的目的!但是,我們雖然沒有修改函數myfunc定義中的代碼,但是我們似乎修改了調用部分的代碼。原本我們是這樣調用的:myfunc(),修改以后變成了:timeit(myfunc)。這樣的話,如果myfunc在N處都被調用了,你就不得不去修改這N處的代碼。或者更極端的,考慮其中某處調用的代碼無法修改這個情況,比如:這個函數是你交給別人使用的。

其實將函數作為參數傳遞,已經具備了裝飾器的雛形了,但是上面的實現還不夠好,下面會給出更好地實現方式。

二、什么是裝飾器decorator

一般而言,如果我需要給函數添加額外的某一些功能,我需要修改函數的源代碼,但是如前面所說,這樣麻煩,而且不高效,裝飾器就是專門的解決方案!

1. 什么是裝飾器?——兩個層面

在Python里面有兩層定義:

第一:從設計模式的層面上

裝飾器是一個很著名的設計模式,經常被用于有切面需求的場景,較為經典的應用有插入日志、增加計時邏輯來檢測性能、加入事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼并繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能

第二:從Python的語法層面上(其實第二種本質上也是第一種,只不過在語法上進行了規范化)

簡言之,python裝飾器就是用于拓展原來函數功能的一種函數,這個函數的特殊之處在于它的返回值也是一個函數,使用python裝飾器的好處就是在不用更改原函數的代碼前提下給函數增加新的功能。 如此一來,我們要想拓展原來函數代碼,就不需要再在函數里面修改源代碼了。

2. 裝飾器的作用——兩方面

(1)抽離雷同代碼,加以重用

(2)為函數添加額外的功能

3. 裝飾器的使用場景

(1)緩存裝飾器

(2)權限驗證裝飾器

(3)計時裝飾器

(4)日志裝飾器

(5)路由裝飾器

(6)異常處理裝飾器

(7)錯誤重試裝飾器

三、裝飾器的實現

1. 裝飾器的逐步實現

針對上面改進版的代碼所存在的哪些問題,我們想出了解決辦法:

既然修改N處的調用代碼很麻煩,我們就來想想辦法不修改調用代碼;如果不修改調用代碼,也就意味著調用myfunc()需要產生調用timeit(myfunc)的效果。

因為python中一切皆對象,故而我們可以想到將timeit賦值給myfunc

代碼如下:

import timedef myfunc():print("我是函數myfunc")def timeit(function):start = time.time()function()end =time.time()print(f'函數執行所花費的時間為:{end-start}')myfunc=timeit  #將timeit賦值給原來的myfunc
myfunc()

但是上面的調用并不會成功,會顯示出如下錯誤:

TypeError: timeit() missing 1 required positional argument: 'function'

這是因為將timeit賦值給myfunc之后,此時myfunctimeit表示的是同一個東西,但是timeit似乎帶有一個參數function,而在調用myfunc()的時候并沒有傳入任何參數,所以并不會成功。

但是上面的調用雖然沒有成功,卻給我們指出了一條重要的線索,因為上面的代碼已經解決“修改調用代碼”的問題,只不過是參數沒有統一而已,那就想辦法把參數統一吧!那就再添加一個函數唄!什么意思?

因為參數不統一,如果timeit()并不是直接添加額外的功能,而是返回一個與myfunc參數列表一致的函數。而原來timeit需要添加額外功能的代碼再在timeit里面定義一個函數,由它去完成不就可以了嗎,將timeit(myfunc)的返回值賦值給myfunc,然后,調用myfunc()的代碼完全不用修改。——即我們依然是調用myfunc(調用代碼沒變),但是同樣卻達到了添加額外功能的效果!

代碼如下:

import time
#原來的函數myfunc
def myfunc():print("我是函數myfunc")#定義一個計時器
def timeit(function):'''timeit函數負責返回一個wrapper,wrapper的參數要與原來的myfunc保持相同這樣一來,執行 myfunc=timeit(myfunc)  myfunc完全等價于wrapperwrapper函數負責添加額外功能'''def wrapper():start = time.time()function()end =time.time()print(f'函數執行所花費的時間為:{end-start}')return wrappermyfunc=timeit(myfunc)  #注意,這里與前面的 “myfunc=timeit”是有所區別的哦
myfunc()  #還和原來調用myfunc()一樣,但是達到了添加額外功能的效果

執行結果:

我是函數myfunc
函數執行所花費的時間為:1.8596649169921875e-05

總結:在上面的函數定義和調用中,看起來我的調用myfunc()和原來并沒有任何不同,但是卻已經添加了額外的效果。它解決前面存在的兩個問題

(1)不用修改函數源代碼,也不用修改調用函數的代碼,完全跟調用最原始的myfunc()代碼一樣,但是卻添加了額外功能;

(2)解決了timeit和myfunc的參數不統一問題,那就是再添加一層wrapper;

——這就是裝飾器。

上面的裝飾器就是最原始的版本,但是python中引入了專門的“語法糖”來實現裝飾器,這樣看起來更加專業,更加美觀。就是使用字符 @ 去實現。代碼如下:

import time#定義一個計時器
def timeit(function):'''timeit函數負責返回一個wrapper,wrapper的參數要與原來的myfunc保持相同這樣一來,執行 myfunc=timeit(myfunc)  myfunc完全等價于wrapperwrapper函數負責添加額外功能'''def wrapper():start = time.time()function()end =time.time()print(f'函數執行所花費的時間為:{end-start}')return wrapper#myfunc=timeit(myfunc)  #注意,這里與前面的 “myfunc=timeit”是有所區別的哦#原來的函數myfunc
@timeit
def myfunc():print("我是函數myfunc")myfunc()  #還和原來調用myfunc()一樣,但是達到了添加額外功能的效果

輸出結果同樣是:

我是函數myfunc
函數執行所花費的時間為:1.7881393432617188e-05

在上面的例子中,在定義myfunc函數的上面加了一個 @timeit,這與前面的寫法 myfunc = timeit(myfunc) 完全等價,

@有兩個重要的作用,第一:較少了代碼書寫量;第二:那就是讓我們的代碼看上去更有裝飾器的感覺,看起來更高端了。

總結

在這個例子中,函數進入和退出時需要計時,這被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統編程習慣的從上往下執行方式相比較而言,像是在函數執行的流程中橫向地插入了一段邏輯。在特定的業務領域里,能減少大量重復代碼。面向切面編程還有相當多的術語,這里就不多做介紹,感興趣的話可以去找找相關的資料(如果有需要,我后面也會抽時間專門寫一系列關于面向切面編程的文章,看我有沒有時間啦!)

2. 裝飾器的一般結構

為了能夠明確裝飾器的實現原理,這里給出一個關于裝飾器的 “一般模板” ,方便大家理解!但是,裝飾器作為一種設計模式,本身是沒有固定的設計模板的,語法也是相對較為靈活,沒有說一定要怎么寫才正確

模板如下:

def decorator(function):'''第一層函數為裝飾器名稱function:參數,即需要裝飾的函數return:返回值wrapper,為了保持與原函數參數一致'''def wrapper(*arg,**args):'''內層函數,這個函數實現“添加額外功能”的任務*arg,**args:參數保持與需要裝飾的函數參數一致,這里用*arg和**args代替'''#這里就是額外功能代碼function()   #執行原函數#這里就是額外功能代碼return wrapper

一般就按照上面這個模板寫“裝飾器”函數,一般就不會出錯了。

四、裝飾器的各種花式實現

學過裝飾器的人都知道Python的閉包,關于“閉包”的詳細定義有各種版本,但我們經常看見這樣一句話,“Python的裝飾器就是一種閉包或者是Python的閉包其實就是裝飾器”,這句話在一定程度上是不正確的,但是這么說也可以(心里要明白二者的本質)。

本質:python閉包是裝飾器的真子集,即裝飾器是更加寬泛的概念,至于為什么,它們二者的區別和聯系,我會在

Python高級編程——裝飾器Decorator詳解(上篇)

中繼續講解python閉包和裝飾器的區別和聯系。

不僅如此,上面所實現的裝飾器是針對函數的,實際上Python的裝飾器可以是“函數”或者是“類”,而被裝飾的對象也可以是“函數”或者是“類”,這樣一來,就有四種搭配情況,即:

  • 函數裝飾函數

  • 函數裝飾類

  • 類裝飾函數

  • 類裝飾類

具體每一種怎么實現呢?其實他們的設計思想都是大同小異,只是實現細節略有不同,欲知詳細情況,且聽下回分解!!!

下一篇預告:

裝飾器與閉包的聯系和區別

四大類裝飾器的搭配實現

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532636.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532636.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532636.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

xss原理和注入類型

XSS漏洞原理 : XSS又叫CSS(cross Site Script), 跨站腳本攻擊,指的是惡意攻擊者往Web頁面里插入惡意JS代碼,當用戶瀏覽該頁時,嵌入其中的Web里的JS代碼就會被執行,從而達到惡意的特殊目的. 比如:拿到cooike XSS漏洞分類: 反射性(非存儲型) payload沒有經過存儲,后端接收后,直接…

Python 裝飾器詳解(中)

Python 裝飾器詳解(中) 轉自:https://blog.csdn.net/qq_27825451/article/details/84581272,博主僅對其中 demo 實現中不適合python3 版本的語法進行修改,并微調了排版,本轉載博客全部例程博主均已親測可行…

存儲型xss案例

存儲型xss原理: 攻擊者在頁面插入xss代碼,服務端將數據存入數據庫,當用戶訪問存在xss漏洞的頁面時,服務端從數據庫取出數據展示到頁面上,導致xss代碼執行,達到攻擊效果 案例: 在一個搭建的論壇網站中, 根據存儲型xss注入的條件,要找到可以存儲到數據庫的輸入位置,并且這個位置…

反射型XSS案例

**原理:**攻擊者將url中插入xss代碼,服務端將url中的xss代碼輸出到頁面上,攻擊者將帶有xss代碼的url發送給用戶,用戶打開后受到xss攻擊 需要url中有可以修改的參數 案例: 可能存在反射型xss的功能(點) : 搜索框等(所有url會出現參數的地方都可以嘗試)……

Python 裝飾器詳解(下)

Python 裝飾器詳解(下) 轉自:https://blog.csdn.net/qq_27825451/article/details/84627016,博主僅對其中 demo 實現中不適合python3 版本的語法進行修改,并微調了排版,本轉載博客全部例程博主均已親測可行…

xss-lab靶場通關writeup(1~6.......在更新)

level 2 : 標簽被編碼&#xff0c;利用屬性完成彈窗 輸入 發現沒有彈窗 查看源代碼&#xff1a; 發現&#xff1a; <>符號被編碼 說明keybord參數進行了處理&#xff0c;那么只能從屬性上進行惡意編碼&#xff1a;先將屬性的引號和標簽閉合&#xff0c;用 // 將后面的…

PyTorch 分布式訓練DDP 單機多卡快速上手

PyTorch 分布式訓練DDP 單機多卡快速上手 本文旨在幫助新人快速上手最有效的 PyTorch 單機多卡訓練&#xff0c;對于 PyTorch 分布式訓練的理論介紹、多方案對比&#xff0c;本文不做詳細介紹&#xff0c;有興趣的讀者可參考&#xff1a; [分布式訓練] 單機多卡的正確打開方式…

Linux free 命令詳解

Linux free 命令詳解 free 命令用來查看系統中已用的和可用的內存。 命令選項及輸出簡介 關于各種命令的功能和命令選項&#xff0c;還是推薦英語比較好的同學直接看手冊 RTFM&#xff1a;man free。這里簡單總結一下一些重點&#xff1a; 功能及輸出簡介 free 命令顯示系…

CTF web題 wp:

1.簽到題 火狐F12查看源碼&#xff0c;發現注釋&#xff1a; 一次base64解碼出flag 2.Encode 在這里插入圖片描述 和第一題界面一樣&#xff1f;&#xff1f; 輕車熟路f12&#xff1a; 發現編碼&#xff1a; 格式看上去是base64&#xff0c;連續兩次base64后&#xff0c;觀…

【深度學習】深入理解Batch Normalization批歸一化

【深度學習】深入理解Batch Normalization批歸一化 轉自&#xff1a;https://www.cnblogs.com/guoyaohua/p/8724433.html 這幾天面試經常被問到BN層的原理&#xff0c;雖然回答上來了&#xff0c;但還是感覺答得不是很好&#xff0c;今天仔細研究了一下Batch Normalization的原…

ThinkPHP V5 漏洞利用

ThinkPHP 5漏洞簡介 ThinkPHP官方2018年12月9日發布重要的安全更新&#xff0c;修復了一個嚴重的遠程代碼執行漏洞。該更新主要涉及一個安全更新&#xff0c;由于框架對控制器名沒有進行足夠的檢測會導致在沒有開啟強制路由的情況下可能的getshell漏洞&#xff0c;受影響的版本…

Vim 重復操作的宏錄制

Vim 重復操作的宏錄制 轉自&#xff1a;https://www.cnblogs.com/ini_always/archive/2011/09/21/2184446.html 在編輯某個文件的時候&#xff0c;可能會出現需要對某種特定的操作進行許多次的情況&#xff0c;以編輯下面的文件為例&#xff1a; ; ;This is a sample config…

Vim 進階1

Vim 進階1 所有你覺得簡單重復&#xff0c;可以自動化實現的操作&#xff0c;都是可以自動化實現的。 Vim光標移動拾遺 w&#xff1a;下一個單詞的開頭&#xff0c;e&#xff1a;下一個單詞的結尾&#xff0c;b&#xff1a;上一個單詞的開頭&#xff0c; 0&#xff1a;行首…

攻防世界web題ics-06(爆破id值)

打開界面&#xff1a;嚯&#xff01;這花里胡哨 點來點去只有報表中心有回顯&#xff1a; 發現url中id等于1&#xff0c;sql注入嘗試無果&#xff0c; burp工具爆破id 對id的值進行爆破 burp報ERROR的話這是個bug&#xff0c;先點擊Hex后點decimal手動刷新就可以使用 強行總…

crontab用法與實例

crontab用法與實例 本文基于 ubuntu 18.04 在Linux系統的實際使用中&#xff0c;可能會經常碰到讓系統在某個特定時間執行某些任務的情況&#xff0c;比如定時采集服務器的狀態信息、負載狀況&#xff1b;定時執行某些任務/腳本來對遠端進行數據采集等。這里將介紹下crontab的配…

手工sql注入常規總結

1.發現注入點 2.報數據庫 先用單引號&#xff08;也嘗試雙引號&#xff09;閉合前面的語句&#xff0c;使注入的語句能夠執行&#xff0c; 數字 0 :匹配字段&#xff0c;還有 11 12 等等都可以使用&#xff0c;有些網站會有過濾處理&#xff0c;建議采用 1%2b12 1%2b1>1 繞…

Systemd入門教程:命令篇

Systemd入門教程&#xff1a;命令篇 轉自&#xff1a;http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 7日 Systemd 是 Linux 系統工具&#xff0c;用來啟動守護進程&#xff0c;已成為大多數…

【CVE-2018-12613】phpmyadmin 4.8.1 遠程文件包含漏洞復現

**環境&#xff1a;**http://62.234.56.138:8080/server_databases.php 官網下載phpmyadmin 4.8.1 源碼&#xff1a;index.php文件中 函數含義&#xff1a; targer非空targer是否位字符串不能以index為開頭&#xff0c;即過濾了index值不能出現在blacklist內&#xff0c;即…

Systemd 入門教程:實戰篇

Systemd 入門教程&#xff1a;實戰篇 轉自&#xff1a;https://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html 作者&#xff1a; 阮一峰 日期&#xff1a; 2016年3月 8日 上一篇文章&#xff0c;我介紹了 Systemd 的主要命令&#xff0c;今天介紹如何使…

關于ubuntu自定義service服務時找不到/usr/lib/systemd/system目錄的問題

關于ubuntu自定義service服務時找不到/usr/lib/systemd/system目錄的問題 問題 我們知道在 systemd 取代了 init 而成為廣大 Linux 系統中 PID 為1的守護進程之后&#xff0c;Linux 中的服務&#xff08;service&#xff09;主要有 systemd 命令組來實現。在大多數發行版 Lin…