Python 裝飾器詳解(中)

Python 裝飾器詳解(中)

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

Python 3.8.5

ubuntu 18.04

聲明:此文章為,python裝飾器詳解——中篇,上一篇文章中,即詳解裝飾器——上篇 ,已經詳細講解了裝飾器誕生的背景,裝飾器的定義、作用、應用場景,本文將以實際例子為依托,深入詳解裝飾器的各類實現(包括函數裝飾器、類裝飾器、閉包、裝飾器的嵌套四大塊內容)系列文章共分為 上、中、下 三篇。此為第二篇。

一、函數裝飾器

前面提到過,裝飾器分為函數裝飾器、類裝飾器,本節詳細解釋函數裝飾器,又分為兩種情況,因為函數裝飾器可以裝飾函數,也可以裝飾類。函數裝飾器是最常見的,故而最先討論。

注意:因為沒有參數,且無函數返回值的函數是最為簡單的,就如“上篇”所討論的那樣,這里就不再敘述了,本文主要講的都是函數帶有參數,而且具有函數返回值的函數。

1. 函數裝飾器裝飾一個函數

假定有一個函數實現兩個數的相加,a+b,但是為了對這兩個數相加的結果進行加密,我們需要給函數添加額外的代碼,但是我們通過裝飾器去實現,要達到的效果是,不是直接返回a+b的結果,而是進行進一步處理。代碼如下:

def MethodDecoration(function):  #外層decoratorc=150d=200def wrapper(a,b):            #內層wrapper。和add_function參數要一樣result=function(a,b)result=result*c/d        #加密,相當于添加額外功能return result            #此處一定要返回值return wrapper@MethodDecoration
def add_function(a,b):return a+bresult=add_function(100,300)    #函數調用
print(result)

運行結果為:300.0 ,即(100+300)*150/200。

因為函數裝飾器去裝飾函數最為常見,所以這里就不多再解釋了,按照前面上篇所講的模板來即可,但是因為被裝飾的函數有參數,而且具有返回值,有兩個點需要注意的

  1. wrapper需要保證與add_function參數一致。因為返回的wrapper就是add_function,所以要統一,我們可以使用*arg**kwargs去匹配任何參數;

  2. wrapper一定要返回值。因為add_function函數是需要返回值的。

2. 函數裝飾器裝飾一個類

在Python中,從某種意義上來說,函數和類是一樣的,因為它們都是對象(python一切皆對象),故而decorator的參數理所當然也可以傳入一個類了。其中最經典的應用,就是使用裝飾器構造“單例模式”(不明白單例模式的小伙伴可以參見下面這篇博文哦)

一文詳解“單例模式”及其python語言的實現

代碼如下:

def Singleton(cls):   #這是第一層函數,相當于模板中的Decorator.目的是要實現一個“裝飾器”,而且是對類型的裝飾器'''cls:表示一個類名,即所要設計的單例類名稱,因為python一切皆對象,故而類名同樣可以作為參數傳遞'''instance = {}def singleton(*args, **kwargs):  #這是第二層,相當于wrapper,要匹配參數if cls not in instance:instance[cls] = cls(*args, **kwargs)   #如果沒有cls這個類,則創建,并且將這個cls所創建的實例,保存在一個字典中return instance[cls]        #返回創建的對象return singleton@Singleton
class Student(object):def __init__(self, name,age):self.name=nameself.age=ages1 = Student('張三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep='   ')

運行結果為:

True
True
140506831296352   140506831296352

懂得單例模式的小伙伴可能一看就明白了,上面的實現和我前面講過的“裝飾器模板”,基本上一樣,第一層、第二層、返回值、參數匹配等。但是有的小伙伴可能會問,這里我沒有看到“添加額外功能”啊,裝飾器不是添加額外功能的么?實際上“添加額外功能”是一種抽象的表述,不是說一定要添加什么東西,對被裝飾的對象(函數、類)進行某種約束、處理、添加、刪減等額外操作統稱為添加額外功能

這里約束了這個類型Student的創建,主要這個類還沒有創建實例,就創建一個,只要創建了,就不在創建新的實例了,只需要把之前創建的返回即可,這是單例模式的思想。如果還是不明白,下面再舉一個“添加額外功能”的例子。

比如我有一個學生類,在創建學生實例的時候有兩個實例屬性,name,age,現在要通過裝飾器對類加以裝飾,使得在創建學生類的實例的時候,還會添加height和weight兩個屬性,代碼如下:

def ClassDecorator(cls):  #第一層函數decoratorheight=170weight=65def wrapper(name,age): #第二層函數wrapper,參數要和類的構造函數匹配s=cls(name,age)s.height=height    #添加兩個額外屬性s.weight=weightreturn s           #返回創建的對象,因為類的構造函數是要返回實例的,即有返回值return wrapper@ClassDecorator
class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('張三',25)
print(stu.name)
print(stu.age)
print(stu.height)    #在 IDE中可能會有提示此處錯誤,學生沒有height和weight屬性,但是運行之后沒錯
print(stu.weight)    #這就是python的魅力,動態添加屬性

運行結果為:

張三
25
170
65

上面的例子和我們前面講的裝飾函數實在是太像了,基本上和前面講的模板一模一樣。

**總結:**函數裝飾器不管是裝飾函數、還是裝飾類,所遵循的思想原理是一樣的,實現的方式也是大同小異。注意,函數裝飾器裝飾類,實際上是裝飾類的構造函數哦!

二、類裝飾器

前面定義的裝飾器都是函數,實際上,類也可以是一個裝飾器,同樣的道理,類裝飾器既可以裝飾函數,也可以裝飾類。

1. 類裝飾器裝飾函數

先從一個簡單的例子說起,代碼如下:

class MethodDecorator:def __init__(self,function):self.function=functiondef __call__(self):print('開始')self.function()print('結束')@MethodDecorator
def myfunc():print('我是函數myfunc')myfunc()

運行結果為:

開始
我是函數myfunc
結束

可能有的小伙伴很懵逼,怎么會得到這樣的結果?我們一句一句來分析。

@MethodDecorator
def myfunc():print('我是函數myfunc')myfunc()

這里相當于 myfunc=MethodDecorator(myfunc),這樣一寫就明白了,首先myfunc函數作為參數傳遞給類的構造函數,因為調用類的構造函數自然會返回類的一個實例對象,所以前面的myfunc實際上是類的一個實例對象,然后調用myfunc,這里雖然從形式上看依然是看起來還是調用函數,但是本質上已經發生了變化,它實際上一個對象調用(這是類裝飾器的本質,很重要),這就是為什么要定義__call__魔法方法。下面比如函數有返回值,而且有參數,要用類裝飾器去裝飾這個函數,再用一個實例說明,依然以上面的兩個數據值和進行加密為例。

class MethodDecorator:def __init__(self,function):  #這里相當于是第一層,作用是將函數function傳遞進來self.function=functionself.c=150      #這兩個是需要加密的額外信息self.d=200def __call__(self,a,b):  #這相當于是第二層的wrapperprint('開始')result=self.function(a,b)result=result*self.c/self.dprint('結束')return result     #返回值@MethodDecorator
def add_function(a,b):return a+bresult=add_function(100,300)  #這里相當于是函數調用
print(result)

運行結果為:

開始
結束
300.0

總結:實際上類裝飾器所實現的功能在原理上和函數裝飾器也沒有太大的區別,但是在代碼實現上有所區別,主要體現在兩方面:

  1. 類裝飾器的構造函數__init__就相當于是第一層(外層)的 decorator,傳入需要裝飾的對象作為參數;

  2. 類裝飾器的魔法方法__call__相當于是第二層(內層)的 wrapper。注意參數要統一,有返回值需要返回值。

2. 類裝飾器裝飾類

依然以上面的給學生添加額外屬性為例

class ClassDecorator:def __init__(self,cls):  #這里相當于是第一層,作用是將類名Student傳遞進來self.cls=clsself.height=170self.weight=65def __call__(self,name,age):  #這相當于是第二層的wrappers=self.cls(name,age)s.height=self.height      #動態添加屬性,增加額外信息s.weight=self.weightreturn s                  #返回創建的學生實例s@ClassDecorator
class Student:def __init__(self,name,age):self.name=nameself.age=agestu=Student('張三',25)   #注意:這里的Student其實并不是一個類了,而是裝飾器返回的一個對象,即這里的Student是ClassDecorator的實例#而且,這里的Student('張三',25) 也不是構造函數了,它的本質是裝飾類的“對象調用”
print(stu.name)
print(stu.age)
print(stu.height)
print(stu.weight)

輸出結果為:

張三
25
170
65

總結:這里的Student其實并不是一個類了,而是裝飾器返回的一個對象,即這里的Student是ClassDecorator的實例,而且,這里的Student(‘張三’,25) 也不是構造函數了,它的本質是裝飾類的“對象調用”

三、類裝飾器的一般模板

通過上面的例子,不管類裝飾器是裝飾類,還是裝飾函數,它的模板都是大同小異的,如下所示:

class ClassDecorator:        #類裝飾器的名稱def __init__(self,function_or_cls):  #這里相當于是第一層,作用是將需要裝飾的類名、或者是函數名傳遞進來#這里可以添加額外信息self.cls=cls         #或者是self.function=function,本質是要構造 一個屬性#這里可以添加額外信息def __call__(self,name,age):  #這相當于是第二層的wrapper,參數需要與被裝飾的類、被裝飾的函數,參數相同#這里可以增加額外信息s=self.cls(name,age)       #本質是調用原來的函數或者類的構造函數#result=self.function(a,b) #這里可以增加額外信息return s                  #返回創建的學生實例s

注意:類裝飾器,對象調用__call__是不可或缺的哦。

四、裝飾器的缺點

前面講了一大堆裝飾器的優點:簡化代碼,代碼復用;增加額外功能等。那么裝飾器優缺點嗎,當然有了,世界上就沒有完美無缺的東西!那到底有一些什么樣的缺點呢?其實在上面的表述中已經提到了,不知道小伙伴有沒有注意!

(以下代碼在上面的代碼代碼基礎上執行,上面代碼這里就不重復一遍了)

1. 函數裝飾器裝飾函數的時候

在上面源代碼的基礎之上添加下面的代碼:

print(add_function.__name__)

輸出為:

wrapper

這是為什么,如果add_function沒有被裝飾器修飾的話,則返回的應該為add_function,這里為什么會返回第二層包裝函數wrapper的名稱?這是因為裝飾器的本質是add_function=MethodDecoration(add_function),而MethodDecoration返回的本來就是wrapper,這就是上面結果的解釋了。

2. 函數裝飾器裝飾類的時候

同樣添加一句代碼

print(Student.__name__)

返回的結果是:wrapper

出現這個現象的原因同上面1中所述的,完全一樣。

3. 類裝飾器裝飾類的時候

同樣的添加下面一句話

print(add_function.__name__)  #這里IDE不會提示錯誤哦,IDE依然覺得這是個函數,應該有__name__才對的

顯示:

AttributeError: 'MethodDecorator' object has no attribute '__name__'

這里為什么突然不一樣了呢?正如前面所說的,這里的add_function本質上是add_function=MethodDecorator(add_function),所以add_function本質上是裝飾類的一個實例,而MethodDecorator沒有定義__name__屬性,那自然調用add_function.__name__就會顯示沒有__name__這個屬性了。

4. 類裝飾器裝飾類的時候

print(Student.__name__)  #這里IDE不會提示錯誤哦,IDE依然覺得這是個類名,應該有__name__才對的

顯示:

AttributeError: 'ClassDecorator' object has no attribute '__name__'

原因同上面一樣,因為Student本質上是ClassDecorator的一個對象實例哦!

裝飾器的缺點總結

  1. 被函數裝飾器所裝飾的對象(函數、類)已經不再是它本身了,雖然從形式上看沒有變化,本質上是函數裝飾器的內部wrapper;

  2. 被類裝飾器所裝飾的對象(函數、類)也不再是它本身了,雖然從形式上看沒有變化,本質上是類裝飾器的一個對象。

補充:關于裝飾器的嵌套,裝飾器與python閉包的關系,我會在系列文章:Python高級編程——裝飾器Decorator詳解(下篇)中繼續講解,有興趣的繼續關注!

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

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

相關文章

存儲型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…

攻防世界web2(逆向加密算法)

打開網頁有如下代碼&#xff1a; <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;$_cchr($__);$_$_.$…

ctags 基本使用方法

ctags 基本使用方法 簡介 ctags&#xff08;Generate tag files for source code&#xff09;是vim下方便代碼閱讀的工具。盡管ctags也可以支持其它編輯器&#xff0c;但是它正式支持的只有 Vim。并且 Vim 中已經默認安裝了 ctags&#xff0c;它可以幫助程序員很容易地瀏覽源…