溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python函數(shù)閉包是什么?怎么實(shí)現(xiàn)裝飾器

發(fā)布時(shí)間:2020-05-22 15:38:37 來源:億速云 閱讀:290 作者:鴿子 欄目:編程語(yǔ)言

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

  一、閉包是什么

  官方定義:

  在計(jì)算機(jī)科學(xué)中,閉包(英語(yǔ):Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。閉包在運(yùn)行時(shí)可以有多個(gè)實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例。

  我的理解

  在一個(gè)函數(shù)的內(nèi)部嵌套了一個(gè)函數(shù),并且這個(gè)函數(shù)引用了外部函數(shù)中的局部變量,那么這個(gè)內(nèi)部函數(shù)就稱為閉包

  Python 中的閉包

  Python 中閉包的實(shí)現(xiàn)得益于Python是一門面向?qū)ο缶幊痰恼Z(yǔ)言,函數(shù)在Python中也不例外,在Python中函數(shù)也是作為對(duì)象(函數(shù)對(duì)象)的方式進(jìn)行存在

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

  

Python函數(shù)閉包是什么?怎么實(shí)現(xiàn)裝飾器


  在這里我們首先定義了一個(gè)函數(shù),然后我們使用Python中的內(nèi)部方法(type),type(args)的作用是查看傳入?yún)?shù)args的類型,我們可以看到func的類型就是一個(gè)函數(shù)對(duì)象

  我們都知道函數(shù)是可以返回值的,所以在Python中函數(shù)對(duì)象也可以作為值進(jìn)行返回,這樣就為閉包的實(shí)現(xiàn)提供了基礎(chǔ).

  下面我們來看一下Python的閉包實(shí)現(xiàn)過程:

  

Python函數(shù)閉包是什么?怎么實(shí)現(xiàn)裝飾器


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

  我們?cè)賮砜纯撮]包的一些屬性

  我們調(diào)用并執(zhí)行outer函數(shù)并將其返回值賦給變量f,我們通過打印可以看到,變量f的類型為函數(shù)類型,因?yàn)槭呛瘮?shù)對(duì)象所以f必然是具有 ‘_call_’ 屬性的(就是函數(shù)后面的() 也就是將函數(shù)執(zhí)行) 然后我們?cè)賮砜磃變量的名稱,可以看到f是名為inner的函數(shù)對(duì)象,為什么會(huì)這樣是因?yàn)?在outer函數(shù)執(zhí)行完畢后,在其返回的時(shí)候我們將在outer函數(shù)內(nèi)部定義的inner函數(shù)對(duì)象進(jìn)行返回了,所以f變量的名稱為inner

  我們既然了解了python中閉包的基本實(shí)現(xiàn)方法,那我們?cè)賮矶嗫磶讉€(gè)例子:

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

  0

  1

  2

  這樣呢? 那我這里就不賣關(guān)子了,其實(shí)上面的這種結(jié)果是錯(cuò)誤的,正確的結(jié)果是:

  2

  2

  2

  為什么會(huì)這樣呢?

  那是因?yàn)樵趏uter函數(shù)內(nèi),inner相對(duì)于outer來說其只是內(nèi)部定義的一個(gè)函數(shù),變量 i 的作用域還是在outer中,所以

  現(xiàn)在inner對(duì)于outer來說并不是閉包,所以 i 的改變是可以改變inner內(nèi)部變量i的值的.當(dāng)循環(huán)最后執(zhí)行完畢時(shí),i變量的值是2 這時(shí)i就不會(huì)改變了,然后在返回的是這個(gè)閉包列表,所以打印出來的應(yīng)該全是2

  那么我們?cè)賮砜匆粋€(gè)例子,假如現(xiàn)在我有一個(gè)需求,內(nèi)容是我需要有一個(gè)函數(shù),我在調(diào)用這個(gè)函數(shù)時(shí)打印出當(dāng)前是第幾次調(diào)用,如果了解迭代器的小伙伴應(yīng)該實(shí)現(xiàn)起來很輕松,但是這里需要用閉包的方式實(shí)現(xiàn)這個(gè)函數(shù),怎么實(shí)現(xiàn)呢.

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

  

Python函數(shù)閉包是什么?怎么實(shí)現(xiàn)裝飾器

        鄭州人流??漆t(yī)院 http://www.03912316666.com/

  但是很遺憾的告訴你,這樣的做法是錯(cuò)誤的,不妨我們來看一下運(yùn)行結(jié)果:

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

  我們來看一下修改后的執(zhí)行效果:

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

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

  ““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”對(duì)象用于實(shí)現(xiàn)由多個(gè)作用域引用的變量。對(duì)于每個(gè)這樣的變量,創(chuàng)建一個(gè)cell對(duì)象來存儲(chǔ)值;引用該值的每個(gè)堆棧幀的局部變量包含對(duì)來自外部作用域的單元格的引用,外部作用域也使用該變量。當(dāng)訪問該值時(shí),將使用單元格中包含的值,而不是單元格對(duì)象本身。單元格對(duì)象的取消引用需要生成的字節(jié)碼的支持;這些不會(huì)在訪問時(shí)自動(dòng)取消引用。單元格對(duì)象不太可能在其他地方有用。

  這樣大家應(yīng)該就明白了.

  二、為什么要有閉包

  閉包避免了使用全局變量,此外,閉包允許將函數(shù)與其所操作的某些數(shù)據(jù)(環(huán)境)關(guān)連起來。這一點(diǎn)與面向?qū)ο缶幊淌欠浅n愃频?,在面?duì)象編程中,對(duì)象允許我們將某些數(shù)據(jù)(對(duì)象的屬性)與一個(gè)或者多個(gè)方法相關(guān)聯(lián)。

  一般來說,當(dāng)對(duì)象中只有一個(gè)方法時(shí),這時(shí)使用閉包是更好的選擇。

  下面我們使用兩種不同的方法來實(shí)現(xiàn)同一種需求,在比較下就可以知道那種方式更有優(yōu)越性:

  需求: 我們有一個(gè)名為Number 的數(shù)字對(duì)象,我們要對(duì)其實(shí)現(xiàn)加法運(yùn)算的法則

  用類來實(shí)現(xiàn):

  用閉包來實(shí)現(xiàn)

  我們可以看到用閉包的方式來實(shí)現(xiàn)時(shí)可以使得代碼更為的簡(jiǎn)便,這就是為什要使用閉包的原因.

  閉包的用處 ——裝飾器

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

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

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

  像這樣的

  像這樣用 @ 符號(hào)進(jìn)行修飾的關(guān)鍵字其實(shí)就是裝飾器.

  那么我們先來定義一個(gè)簡(jiǎn)單的裝飾器來看一下:

  上面的 decorator便是我們自定義的一個(gè)裝飾器,在下面我們定義的f函數(shù)我們對(duì)其使用了裝飾器進(jìn)行裝飾,

  我們發(fā)現(xiàn)原本f函數(shù)內(nèi)只有 “>>>> 正在執(zhí)行” 這一行打印信息,但經(jīng)過裝飾器裝飾后,就變成了兩行的打印信息,

  是不是很神奇,凡是都是有原因的,現(xiàn)在就來揭秘一下這里面的玄機(jī).

  其實(shí) @ 符號(hào)在這里的作用就是 讓程序自動(dòng)執(zhí)行一條這樣的語(yǔ)句 “f = decorator(f)” 不妨我們來看看就知道了:

  

Python函數(shù)閉包是什么?怎么實(shí)現(xiàn)裝飾器


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

  好了我們?cè)賮砜匆恍└鼜?fù)雜的裝飾器加深理解:

  帶參的裝飾器:

  上面我們實(shí)現(xiàn)來在裝飾器中傳入?yún)?shù)的做法,我們?yōu)楫?dāng)前的函數(shù)取了一個(gè)名字,然后在執(zhí)行的時(shí)候?qū)⑵浯蛴〕鰜?我們發(fā)現(xiàn)要進(jìn)行傳參時(shí),我們整個(gè)函數(shù)的嵌套變?yōu)榱巳龑?當(dāng)然和前面不帶參的裝飾器的工作機(jī)理是差不多的,換湯不換藥. 帶參數(shù)的裝飾器無非在使用的過程中進(jìn)行了如下的操作 f = set_name(“Nick”)(f) 這樣一說應(yīng)該就明白了.

  裝飾器需要裝飾的函數(shù)帶參問題:

  (1) 第一種做法:

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

  (1) 第二種做法:

  和前一種方法效果一樣,但是使用包裹參數(shù)和包裹關(guān)鍵字參數(shù)進(jìn)行傳參,通用性就強(qiáng)很多了,所以推薦這樣寫.

  我們?cè)谥v第一個(gè)裝飾器的時(shí)候大家就發(fā)現(xiàn)了一個(gè)問題,那就是函數(shù)經(jīng)過裝飾器修飾后,原來的函數(shù)名稱就發(fā)生了改變,統(tǒng)一的都命名成了我們裝飾器內(nèi)部定義的函數(shù)名,那我如果還是想要原來的函數(shù)名怎么辦呢,別急有下面的第一種做法:

  這樣做其實(shí)就是強(qiáng)行改變其對(duì)象的名稱,這樣的做法肯定就顯得不是那么優(yōu)雅了,在Python中也是不推崇的.

  我們?cè)賮砜聪旅嬉环N更為優(yōu)雅的寫法:

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

  如果感興趣的小伙伴不妨自己思考一下functools.wraps 這個(gè)裝飾器的實(shí)現(xiàn)思路,然后自己動(dòng)手實(shí)現(xiàn)一次,相信對(duì)你學(xué)習(xí)裝飾器有一定的幫助.這里介于篇幅的原因就不講解實(shí)現(xiàn)的思路了.

  下面來最后一個(gè)例子,我們用類的方式來實(shí)現(xiàn)一次裝飾器看看:

  對(duì)于類中的_call_ 方法在前面已經(jīng)提過在這里就不講了,這里的裝飾器的調(diào)用過程和前面第一種裝飾器的調(diào)用過程是一樣的 f = MyDecorator(f) 相當(dāng)于f就是一個(gè)由MyDecorator類實(shí)例化出來的一個(gè)對(duì)象,當(dāng)這個(gè)對(duì)象f在執(zhí)行f()時(shí)就觸發(fā)了MyDecorator中的__call__方法.

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI