中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python函數閉包是什么?怎么實現裝飾器

發布時間:2020-05-22 15:38:37 來源:億速云 閱讀:298 作者:鴿子 欄目:編程語言

  很多初次接觸到python的小伙伴可能并不理解閉包是什么,為什么有閉包,閉包有什么用,那么今天博主就從這三點來為大家講解一下python的閉包

  一、閉包是什么

  官方定義:

  在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。

  我的理解

  在一個函數的內部嵌套了一個函數,并且這個函數引用了外部函數中的局部變量,那么這個內部函數就稱為閉包

  Python 中的閉包

  Python 中閉包的實現得益于Python是一門面向對象編程的語言,函數在Python中也不例外,在Python中函數也是作為對象(函數對象)的方式進行存在

  下面我們來看一個例子你就清楚了:

  

Python函數閉包是什么?怎么實現裝飾器


  在這里我們首先定義了一個函數,然后我們使用Python中的內部方法(type),type(args)的作用是查看傳入參數args的類型,我們可以看到func的類型就是一個函數對象

  我們都知道函數是可以返回值的,所以在Python中函數對象也可以作為值進行返回,這樣就為閉包的實現提供了基礎.

  下面我們來看一下Python的閉包實現過程:

  

Python函數閉包是什么?怎么實現裝飾器


  在這里我們首先定義了一個名為outer的函數,然后在這個函數的內部我們又定義了一個名為"out_value"的變量,然后又在函數的內部定義了一個名為inner的內部函數,在inner函數內我們又引用了外部函數(outer)的變量,這樣我們就稱inner為閉包

  我們再來看看閉包的一些屬性

  我們調用并執行outer函數并將其返回值賦給變量f,我們通過打印可以看到,變量f的類型為函數類型,因為是函數對象所以f必然是具有 ‘_call_’ 屬性的(就是函數后面的() 也就是將函數執行) 然后我們再來看f變量的名稱,可以看到f是名為inner的函數對象,為什么會這樣是因為,在outer函數執行完畢后,在其返回的時候我們將在outer函數內部定義的inner函數對象進行返回了,所以f變量的名稱為inner

  我們既然了解了python中閉包的基本實現方法,那我們再來多看幾個例子:

  上面這個例子大家在沒有看答案的情況下,大家猜一下結果會是怎樣呢,可能有部分小伙伴已經看透了這其中的貓膩,那部分沒有看出的小伙伴心中的結果是不是

  0

  1

  2

  這樣呢? 那我這里就不賣關子了,其實上面的這種結果是錯誤的,正確的結果是:

  2

  2

  2

  為什么會這樣呢?

  那是因為在outer函數內,inner相對于outer來說其只是內部定義的一個函數,變量 i 的作用域還是在outer中,所以

  現在inner對于outer來說并不是閉包,所以 i 的改變是可以改變inner內部變量i的值的.當循環最后執行完畢時,i變量的值是2 這時i就不會改變了,然后在返回的是這個閉包列表,所以打印出來的應該全是2

  那么我們再來看一個例子,假如現在我有一個需求,內容是我需要有一個函數,我在調用這個函數時打印出當前是第幾次調用,如果了解迭代器的小伙伴應該實現起來很輕松,但是這里需要用閉包的方式實現這個函數,怎么實現呢.

  可能有小伙伴就有了下面的思路:

  

Python函數閉包是什么?怎么實現裝飾器

        鄭州人流專科醫院 http://www.03912316666.com/

  但是很遺憾的告訴你,這樣的做法是錯誤的,不妨我們來看一下運行結果:

  分析錯誤原因,大概的意思就是 局部變量“cnt”在賦值之前引用 ,它告訴我們不能在引用后進行賦值,因為這個變量的作用域還是在外部函數內的,那怎樣解決這樣的問題呢,Python就提供了很好的一個保留字用來聲明 非局部變量( nonlocal 關鍵字)

  我們來看一下修改后的執行效果:

  可以看到通過nonlocal 聲明變量cnt之后就得到了我們想要的結果

  下面是官方對閉包過程中變量作用域的一些解釋:

  ““Cell” objects are used to implement variables referenced by multiple scopes. For each such variable, a cell object is created to store the value; the local variables of each stack frame that references the value contains a reference to the cells from outer scopes which also use that variable. When the value is accessed, the value contained in the cell is used instead of the cell object itself. This de-referencing of the cell object requires support from the generated byte-code; these are not automatically de-referenced when accessed. Cell objects are not likely to be useful elsewhere.”

  “Cell”對象用于實現由多個作用域引用的變量。對于每個這樣的變量,創建一個cell對象來存儲值;引用該值的每個堆棧幀的局部變量包含對來自外部作用域的單元格的引用,外部作用域也使用該變量。當訪問該值時,將使用單元格中包含的值,而不是單元格對象本身。單元格對象的取消引用需要生成的字節碼的支持;這些不會在訪問時自動取消引用。單元格對象不太可能在其他地方有用。

  這樣大家應該就明白了.

  二、為什么要有閉包

  閉包避免了使用全局變量,此外,閉包允許將函數與其所操作的某些數據(環境)關連起來。這一點與面向對象編程是非常類似的,在面對象編程中,對象允許我們將某些數據(對象的屬性)與一個或者多個方法相關聯。

  一般來說,當對象中只有一個方法時,這時使用閉包是更好的選擇。

  下面我們使用兩種不同的方法來實現同一種需求,在比較下就可以知道那種方式更有優越性:

  需求: 我們有一個名為Number 的數字對象,我們要對其實現加法運算的法則

  用類來實現:

  用閉包來實現

  我們可以看到用閉包的方式來實現時可以使得代碼更為的簡便,這就是為什要使用閉包的原因.

  閉包的用處 ——裝飾器

  既然前面講了這么多閉包的知識,和應用,那么這里就來說一下閉包在Python中最大的一個用處,那就是"裝飾器".

  一聽這個名字就知道裝飾器的作用,裝飾器嘛那肯定是用來裝飾的嘛,那它是用來裝飾什么的呢,其實就是用來裝飾Python中的一些對象的.

  可能小伙伴在了解裝飾器前,在接觸到類的時候就看見有的類方法上面有一些特殊的符號.

  像這樣的

  像這樣用 @ 符號進行修飾的關鍵字其實就是裝飾器.

  那么我們先來定義一個簡單的裝飾器來看一下:

  上面的 decorator便是我們自定義的一個裝飾器,在下面我們定義的f函數我們對其使用了裝飾器進行裝飾,

  我們發現原本f函數內只有 “>>>> 正在執行” 這一行打印信息,但經過裝飾器裝飾后,就變成了兩行的打印信息,

  是不是很神奇,凡是都是有原因的,現在就來揭秘一下這里面的玄機.

  其實 @ 符號在這里的作用就是 讓程序自動執行一條這樣的語句 “f = decorator(f)” 不妨我們來看看就知道了:

  

Python函數閉包是什么?怎么實現裝飾器


  咦 我們發現我們定義的函數f它的名字被換成了wrapper,這不正是我們裝飾器函數返回閉包嘛,這樣結合前面的閉包知識,大家發現裝飾器其實也沒那么難嘛.

  好了我們再來看一些更復雜的裝飾器加深理解:

  帶參的裝飾器:

  上面我們實現來在裝飾器中傳入參數的做法,我們為當前的函數取了一個名字,然后在執行的時候將其打印出來,我們發現要進行傳參時,我們整個函數的嵌套變為了三層,當然和前面不帶參的裝飾器的工作機理是差不多的,換湯不換藥. 帶參數的裝飾器無非在使用的過程中進行了如下的操作 f = set_name(“Nick”)(f) 這樣一說應該就明白了.

  裝飾器需要裝飾的函數帶參問題:

  (1) 第一種做法:

  顯然這樣的做法做出來的裝飾器通用性并不強,如果我們需要裝飾的函數參數一發生變化那我們的裝飾器就不能使用了,這肯定是我們不能接受的.

  (1) 第二種做法:

  和前一種方法效果一樣,但是使用包裹參數和包裹關鍵字參數進行傳參,通用性就強很多了,所以推薦這樣寫.

  我們在講第一個裝飾器的時候大家就發現了一個問題,那就是函數經過裝飾器修飾后,原來的函數名稱就發生了改變,統一的都命名成了我們裝飾器內部定義的函數名,那我如果還是想要原來的函數名怎么辦呢,別急有下面的第一種做法:

  這樣做其實就是強行改變其對象的名稱,這樣的做法肯定就顯得不是那么優雅了,在Python中也是不推崇的.

  我們再來看下面一種更為優雅的寫法:

  這下就對了,正如老爹所說我們要用魔法來對付魔法,這里用裝飾器的來解決裝飾器的問題,就顯得優雅許多了,

  如果感興趣的小伙伴不妨自己思考一下functools.wraps 這個裝飾器的實現思路,然后自己動手實現一次,相信對你學習裝飾器有一定的幫助.這里介于篇幅的原因就不講解實現的思路了.

  下面來最后一個例子,我們用類的方式來實現一次裝飾器看看:

  對于類中的_call_ 方法在前面已經提過在這里就不講了,這里的裝飾器的調用過程和前面第一種裝飾器的調用過程是一樣的 f = MyDecorator(f) 相當于f就是一個由MyDecorator類實例化出來的一個對象,當這個對象f在執行f()時就觸發了MyDecorator中的__call__方法.

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

津南区| 田东县| 涟水县| 门源| 卢湾区| 河南省| 临桂县| 湘阴县| 新津县| 怀化市| 邓州市| 政和县| 高清| 旬阳县| 丰县| 潞西市| 西丰县| 伊宁市| 临朐县| 建阳市| 武宁县| 宜章县| 大方县| 新源县| 怀来县| 乌兰浩特市| 竹山县| 含山县| 阳东县| 池州市| 甘南县| 普兰店市| 佳木斯市| 洪泽县| 淳安县| 彩票| 洛南县| 黑水县| 宣汉县| 鄢陵县| 新乐市|