Python 裝飾器詳解(下)

Python 裝飾器詳解(下)

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

Python 3.8.5

ubuntu 18.04

聲明:此文章為,python裝飾器詳解——下篇,上一篇文章中,即詳解裝飾器——中篇 ,已經詳細講解了兩大類裝飾器,即函數裝飾器、類裝飾器的應用實例,并且分析了它們在運行的過程中的本質,給出了類裝飾器的一般模板,本文將以實際例子為依托,講解剩下的兩個內容(閉包和裝飾器的嵌套),其中,閉包是重點,包括閉包的誕生背景,閉包的定義、作用、與裝飾器的關系與區別。該系列文章共分為 上、中、下 三篇。此為第三篇。

一、閉包誕生的背景——closure

1. 一個意想不到的窘境

很多的語言都存在閉包(closure),我們也常常聽起這樣的概念,但是你真的理解它了嗎?東它的本質嗎?在講閉包之前,我打算從一個簡單的情況說起。請先看一個例子:

func_list = []
for i in range(3):def myfunc(a):return i+afunc_list.append(myfunc)  #定義三個函數,將三個函數存放在一個列表中for f in func_list:           #調用列表中的三個函數print(f(1))

上面的運行結果是1 2 3 嗎?但是真是的運行結果確實3 3 3。這是為什么?粗略的分析,第一個函數返回的應該是0+1,第二個返回的應該是1+1 ,第三個返回的應該是 2+1 啊,那為什么會出現這樣的結果呢?從結果上分析,應該三個函數都是返回的 2+1,這是為什么呢?因為函數定義在循環內部,雖然每一次看起來好像 i 分別為 0、1、2,實際上因為函數是沒有辦法去保存這個變化的i 的,也就是說,i,是在函數外面發生變化的,函數里面的i會一直隨著i的變化而變化,直到最終這個i不變化了,那函數里面的i是多少就是多少了。總結起來就一句話:

循環體內定義的函數是無法保存循環執行過程中的不停變化的外部變量的,即普通函數無法保存運行環境!還是不理解?

再看一個簡單的例子:

a=100def myfunc(b):return a+bprint(myfunc(200))a=200
print(myfunc(200))

上面的代碼大家都懂,運行結果為300 400。我們可以發現,因為函數內部有用到外面的a,所以函數運行的結果會隨著這個a的變化而變化,直到外面的a不變了為止,否則光函數傳遞的參數是確定的還不夠,還要取決于a。我們用兩個比較通俗的層面去理解:

  1. 函數內部使用到了a,b,但是a卻不是函數本身具備的財產,我雖然可以使用,但是我卻不能決定它,a變化了,函數的結果就跟著變化了,直到a取最終的值,否則函數都是變化的。(你不確定,我就永遠沒有辦法確定,你雖然就在我我身邊,但是我卻不能真正掌控你,這種感覺難道不難受嗎?)

  2. 用書面語言說,函數沒有辦法保存它的運行環境,什么意思,在上面的兩個例子里面,函數的運行環境都是這個模塊(即py文件),也就是說,在這個運行環境里面的一切,函數都是沒有辦法做主的,函數能夠做主只有他自身的局部作用域(包括形參)。

2. 窘境的解決辦法

func_list = []
for i in range(3):def decorator(i):      #定義一個外層函數,這里之所以使用decorator,是為了后面與“裝飾器進行比較def wrapper(a):    #定義一個內層函數,定義為wrapper是為了后面的比較return i + areturn wrapperfunc_list.append(decorator(i))  for f in func_list:print(f(1))

運行結果為 1 2 3 。關于為什么后面再詳細講解,這里先提供一種解決思路。

二、閉包的定義及應用

1. 閉包的定義

在一些語言中,在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部的函數的變量,則可能產生閉包閉包可以用來在一個函數與一組“私有”變量之間創建關聯關系。在給定函數被多次調用的過程中,這些私有變量能夠保持其持久性。—— 維基百科

2. 閉包的作用

閉包可以用來在一個函數與一組“私有”變量之間創建關聯關系。在給定函數被多次調用的過程中,這些私有變量能夠保持其持久性(保存運行環境與變量狀態

3. 閉包的特征

上面的描述還是不夠精煉,有沒有幾個特別的特征,讓人一眼就看出來它就是閉包呢?

  1. 必須要有函數的嵌套。而且外層函數必須返回內層函數,但是內層函數可以不返回值,也可以返回值;外層函數給內層函數提供了一個 “包裝起來的運行環境”,在這個“包裝的”運行環境里面,內層函數可以完全自己做主。這也是稱之為閉包的原因了。

  2. 內層函數一定要用到外層函數中定義的變量。如果只滿足了特征(1),也不算是閉包,一定要用到外層“包裝函數”的變量,這些變量稱之為 “自由變量”

3. 閉包的代碼解析

依然以上面的那么例子而言,我們提出了解決窘境的辦法,那我們現在來解釋這個解決辦法到底做了什么工作。

func_list = []
for i in range(3):def decorator(i):      #定義一個外層函數,這里之所以使用decorator,是為了后面與“裝飾器進行比較def wrapper(a):    #定義一個內層函數,定義為wrapper是為了后面的比較return i + areturn wrapperfunc_list.append(decorator(i))  for f in func_list:print(f(1))

這里列表中存出的就是三個包裝函數decorator(1)decorator(2)decorator(3),其實相當于三個如下的定義:

def decorator(i=1):def wrapper(a):return i+a

因為這里wrapper的運行環境為decorator,不再是全局的環境,所以在wrapper的環境中,i 是固定的,不會再變化,故而當然能夠自己做主了。

三、閉包的細節

首先明確閉包的兩個核心特征:函數嵌套自由變量

其次明確閉包的兩個核心功能:保存函數的運行環境狀態保存閉包環境內的局部變量

1. 閉包的細節實現

看一個簡單的閉包的例子,為了與前面的系列文章(中篇)的裝飾器進行比較,這里也采用中篇中的案例,我要為一個兩數相加的運算加密:

def decorator(c):  #外層函數,產生包裝環境——即閉包d=200           #c d 都是包裝環境中的局部變量——即自由變量def wrapper(a,b):  #內層函數return (a+b)*c/dreturn wrapperwrapper=decorator(150)
print(wrapper(100,300))

運行結果為:300.0

  1. **為什么說它保存了函數的運行環境?**這里針對函數是內層函數即wrapper,它的運行環境是decorator提供的,也就是說decorator的環境是保存的,什么意思呢,其實就是通過一句話,

    wrapper=decorator(150)
    

    也就是說,這里wrapper運行所依賴的 c 就固定是150了,d 就固定是200了,不會再改變,無論我再給wrapper 傳遞什么參數,cd 是不會在變化的。當然如果我重新再執行一次wrapper=decorator(250),相當于是又創建了一個新的包裝環境,這里的 c 就是250了。

  2. **為什么說它能夠保存閉包函數內的局部變量?**眾所周知,函數的局部變量會隨著函數的調用結束而銷毀,那么為什么局部變量能夠保存呢?這里所說的局部變量指的是閉包函數的局部變量,即上面的 cd。也就是說,我這里的 cd 是保存著的,即使我已經執行wrapper(100,300)執行完畢。

2. 自由變量的查看

我們說閉包函數的局部變量是保存著的,那如何查看呢?我們可以通過內層函數的一個屬性__closure__查看。

print(wrapper.__closure__)
print(wrapper.__closure__[0].cell_contents)
print(wrapper.__closure__[1].cell_contents)

結果如下:

(<cell at 0x7f2c886802b0: int object at 0x5558ee7e6fe0>, <cell at 0x7f2c88680370: int object at 0x5558ee7e7620>)
150
200

可以看到 __closure__ 屬性返回一個元組,而150和200則分別對應自由變量 cd

總結:內層函數的__closure__屬性返回一個元組;通過 wrapper.__closure__[i].cell_contents 查看第幾個自由變量的值

注意:如果閉包函數沒有返回wrapper,即外層函數沒有返回內層函數,此時內層函數是沒有__closure__屬性的。

總結:現在可以體會為什么說閉包保存局部變量了吧,這里的c d 作為局部變量,在函數調用結束后還能夠查看到它的值,這還不是保存,那什么是保存呢?

3. 閉包的一般模板

def decorator(*arg,**kargs):  #外層函數,產生包裝環境——即閉包#自由變量區域                 # 包含形參,都是包裝環境中的局部變量——即自由變量def wrapper(a,b):  #內層函數return (a+b)*c/dreturn wrapperwrapper=decorator(150)      #創建唯一的閉包環境
wrapper(100,300)            #內層函數的調用

四、閉包與裝飾器的比較

1. 相同點

  1. 都是函數的嵌套,分為外層函數和內層函數,而且外層函數要返回內層函數

  2. 代碼的實現邏輯大同小異

  3. 二者都可以實現增加額外功能的目的——比如上面的“加法加密運算”

2. 不同點

  1. 外層函數不同,裝飾器的外層函數稱之為decorator,閉包的外層函數稱之為閉包函數closure

  2. 外層函數的目的不同,裝飾器的外層函數主要是提供函數形參function,閉包的形參主要目的是提供自由變量。

  3. 二者的特征不一樣。裝飾器的外層函數可以不提供自由變量,但是閉包的的外層函數一定要提供自由變量,因為如果不提供自由變量,必報的存在就毫無意義了,即內層函數所依賴的變量卻在閉包中根本沒有,那還要閉包干什么?

  4. 二者的主要目的不同。裝飾器的目的:代碼重用+額外功能。閉包的主要目的:保存函數的運行環境+保存閉包的局部變量。雖然二者可以有一些交集。

  5. 閉包和裝飾器本質上還是不一樣的,但是從形式上來說,大致可以認為閉包是裝飾器的子集。記住:僅僅是從形式上哦

3. 如何理解“閉包”與“裝飾器”的本質不一樣,但是形式類似?

關于形式類似,這里就不說了,參見前面的兩篇文章和這篇文章里面的模板即可,發現他們長得很像。

為什么說本質不一樣?

  1. 因為對與裝飾器而言,我必須要給外層函數 decorator 傳遞一個基本參數 function,只有這樣,我才可以寫成function=decorator(function)或者是 @decorator 的形式,如果沒有這個參數,會顯示以下錯誤:

     decorator() takes 0 positional arguments but 1 was given
    

    decorator 我必須要定義一個 function 參數,否則就會顯示定義沒有參數,但給了它一個參數這種錯誤,因為 function=decorator(function) 或者是 @decorator 這就相當于給了他一個參數。

    不僅如此,裝飾器會改變函數 function 本身的 __name__ 屬性,參見前文。

  2. 但是對于閉包,外層函數就沒有這些要求,也不是一定要定義一個 function 參數,甚至我也可以不定義參數。至于兩者的本質區別,學懂了的小伙伴應該可以自己好好體會了。

五、裝飾器的嵌套

關于裝飾器的多層嵌套,理解起來相對于比較復雜,本文先做一個預告,將在系列文章的下一篇,也就是第四篇進行深入詳解,有需要的可以關注一下。
傳送門:https://blog.csdn.net/qq_27825451/article/details/102457152。

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

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

相關文章

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;它可以幫助程序員很容易地瀏覽源…

vimrc配置文件

vimrc配置文件 轉自&#xff1a;https://www.ruanyifeng.com/blog/2018/09/vimrc.html Vim 是最重要的編輯器之一&#xff0c;主要有下面幾個優點。 可以不使用鼠標&#xff0c;完全用鍵盤操作。系統資源占用小&#xff0c;打開大文件毫無壓力。鍵盤命令變成肌肉記憶以后&am…

CTFHUB 《請求方式》 http請求,curl命令總結

打開網頁&#xff1a; 思路一&#xff1a; 根據題目&#xff0c;應該是向網頁發送get方式請求&#xff0c;但并沒有具體規定要發送什么&#xff0c;嘗試get發送參數后&#xff0c;都沒有返回網頁&#xff0c;emmm’…好像不是我想的那種套路 思路二&#xff1a; 網上找到思路…

Vim進階2 map映射

Vim進階2 map映射 簡介 map是一個 vim 中的一些列映射命令&#xff0c;將常用的很長的命令映射到一個新的功能鍵上。map是Vim強大的一個重要原因&#xff0c;可以自定義各種快捷鍵&#xff0c;用起來自然得心應手。 map系列命令格式 格式 以 map 命令為例&#xff0c;它的…