文章目錄
- 閉包(Closure)
- 嵌套函數(shù)(nested function)
- 閉包的概念
- 如何使用閉包
- 何時定義閉包
- 修改自由變量
- 裝飾器(Decorator)
- 裝飾器介紹
- 實現(xiàn)裝飾功能
- 添加裝飾器
- 含參裝飾器
- 鏈?zhǔn)窖b飾器
閉包(Closure)
嵌套函數(shù)(nested function)
講解閉包之前,先介紹一下什么是嵌套函數(shù)(nested function):
def
print_msg
(
msg
)
:
# This is the outer enclosing function
def
printer
(
)
:
# This is the nested function
print
(
msg
)
printer
(
)
>>
>
print_msg
(
"Hello"
)
Hello
具體是這樣實現(xiàn)的:首先調(diào)用
print_msg
函數(shù),傳入?yún)?shù)
msg
為
Hello
,該函數(shù)調(diào)用的是其內(nèi)嵌套的另一個函數(shù)
printer
,該
printer
函數(shù)使用了非局部變量(non-local)
msg
,完成打印輸出。
閉包的概念
在計算機科學(xué)中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實例。
這里引用的是維基百科對于閉包的解釋。注意這里的兩個關(guān)鍵詞:
自由變量
和
函數(shù)
,也就是說,閉包本質(zhì)上是一個
嵌套函數(shù)
,還包括該函數(shù)引用的
非局部自由變量
。自由變量的意思從字面上理解就是:不受(系統(tǒng))約束的變量,也就是該變量并不會隨著外部函數(shù)的生命周期結(jié)束而被回收。為了方便理解,這里還是通過以上嵌套函數(shù)的例子來講解:在該嵌套函數(shù)中,如果最后
pring_msg
不是調(diào)用
printer
而是返回該函數(shù)(在Python中,函數(shù)作為一等公民,因此可以直接作為對象返回):
def
print_msg
(
msg
)
:
# This is the outer enclosing function
def
printer
(
)
:
# This is the nested function
print
(
msg
)
return
printer
在這里,
printer
函數(shù)就是一個閉包,它包括自由變量
msg
,并且該自由變量并不會隨著
print_msg
函數(shù)的聲明周期結(jié)束而消失:
>>
>
another
=
print_msg
(
"Hello"
)
>>
>
another
(
)
Hello
>>
>
del
print_msg
>>
>
print_msg
(
"Hello"
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
NameError Traceback
(
most recent call last
)
<
ipython
-
input
-
7
-
806baf73d191
>
in
<
module
>
(
)
-
-
-
-
>
1
print_msg
(
'Hello'
)
NameError
:
name
'print_msg'
is
not
defined
>>
>
another
(
)
Hello
通過以上的例子我們可以知道,
another
一開始已經(jīng)被定義為一個閉包函數(shù)了(相當(dāng)于
printer
函數(shù)),并且擁有自由變量
msg
,二者已經(jīng)綁定在一起了,即使
msg
離開了創(chuàng)造它的環(huán)境
print_msg
也能存在。當(dāng)
del print_msg
后,該函數(shù)已經(jīng)不存在了,然后
another
仍然能夠自由使用
msg
變量(自由變量)。
如何使用閉包
正如上面的例子,我們可以很容易地定義一個閉包函數(shù)。那么,當(dāng)你定義閉包的時候需要注意什么呢?
- 首先,必須定義一個嵌套函數(shù)(函數(shù)內(nèi)的函數(shù))
- 其次,該嵌套函數(shù)必須引用外包函數(shù)的參數(shù)
- 外包函數(shù)必須引用其內(nèi)的嵌套函數(shù)
何時定義閉包
那么,閉包有什么好處?
定義閉包可以使得你避免使用全局變量(這在任何語言都是極力避免的,因為全局變量不好控制),并且提供某種形式的數(shù)據(jù)隱藏,還能夠提供一個面向?qū)ο蟮慕鉀Q方案。
當(dāng)在類中實現(xiàn)的方法很少(大多數(shù)情況下只有一個方法)時,閉包可以提供另一種更優(yōu)雅的解決方案。但是當(dāng)屬性和方法的數(shù)量增加時,最好實現(xiàn)一個類。
這里我們舉一個簡單的例子來說明使用閉包比使用類更合適。這里我們需要的實現(xiàn)是一個簡單的倍乘器:
- 使用類實現(xiàn)
這里實現(xiàn)的是一個倍乘器的類(雖然用類實現(xiàn)顯得有點大材小用),可以將
x
擴大
n
倍。
class
make_multiplier_of
(
object
)
:
def
__init__
(
self
,
n
)
:
self
.
n
=
n
def
multiplier
(
self
,
x
)
:
return
x
*
self
.
n
這里定義兩個倍乘器(三倍
times3
與五倍
times5
)
times3
=
make_multiplier_of
(
3
)
times5
=
make_multiplier_of
(
5
)
>>
>
times3
.
multiplier
(
4
)
# 3x4
12
>>
>
times5
.
multiplier
(
4
)
# 5x4
20
- 閉包實現(xiàn)
這里是實現(xiàn)的是一個倍乘器的閉包,功能同上面的類。
def
make_multiplier_of
(
n
)
:
def
multiplier
(
x
)
:
return
x
*
n
return
multiplier
定義兩個倍乘器(三倍
times3
與五倍
times5
)
times3
=
make_multiplier_of
(
3
)
times5
=
make_multiplier_of
(
5
)
>>
>
times3
(
4
)
# 3x4
12
>>
>
times5
(
4
)
# 5X4
20
>>
>
times5
(
times3
(
4
)
)
# 5x3x4
60
- partial
當(dāng)然,這種簡單的功能其實使用Python中的
partial
函數(shù)即可實現(xiàn)了。
def
multiplier
(
x
)
:
return
x
*
n
然后也是定義兩個倍乘器:
from
functools
import
partial
times3
=
partial
(
multiplier
,
n
=
3
)
times5
=
partial
(
multiplier
,
n
=
5
)
這里我們總結(jié)下類的實現(xiàn)與閉包實現(xiàn)的區(qū)別。雖然結(jié)果是一樣的,但是顯然類的實現(xiàn)相當(dāng)繁瑣,這里實現(xiàn)一個倍乘器使用類確實是小題大做了,同時,
make_multiplier_of
函數(shù)在執(zhí)行完畢后,其作用域已經(jīng)釋放,但
make_multiplier_of
類卻不是,它會與它的實例
times3
和
times5
一直貯存在內(nèi)存中,而這種占用對于實現(xiàn)該功能后,顯得十分沒有必要。
修改自由變量
這里我們要注意的是,閉包中引用的自由變量是無法修改的,例如:
def
outer_function
(
)
:
x
=
0
def
inner_function
(
)
:
x
=
1
# try to modify n
print
(
f
"Inner function: x = {x}"
)
print
(
f
"Before: Outer function: x = {x}"
)
inner_function
(
)
print
(
f
"After: Outer function: x = {x}"
)
>>
>
outer_function
(
)
Before
:
Outer function
:
x
=
0
Inner function
:
x
=
1
After
:
Outer function
:
x
=
0
這里我們可以看到
x
變量并沒有被修改,也就是內(nèi)嵌函數(shù)
inner_function()
并不能修改非局部變量
x
,如果想要修改(通常不建議修改),可以使用
nonlocal
關(guān)鍵字:
def
outer_function
(
)
:
x
=
0
def
inner_function
(
)
:
nonlocal
x
x
=
1
# try to modify n
print
(
f
"Inner function: x = {x}"
)
print
(
f
"Before: Outer function: x = {x}"
)
inner_function
(
)
print
(
f
"After: Outer function: x = {x}"
)
>>
>
outer_function
(
)
Before
:
Outer function
:
x
=
0
Inner function
:
x
=
1
After
:
Outer function
:
x
=
1
裝飾器(Decorator)
裝飾器介紹
Python中有一個十分有趣的特性,叫做 裝飾器(Decorator) ,它可以為某些已經(jīng)存在的函數(shù)(代碼)添加某些新的功能,即將該函數(shù)添加一些新功能后返回這個添加了新功能的函數(shù)。這也稱為元編程,因為程序的一部分試圖在編譯時修改程序的另一部分。它可以在不改變現(xiàn)有程序的大體結(jié)構(gòu)上,為其添加新功能,舉個例子:
@new_decorated_function
def
original_function
(
*
args
,
**
kwargs
)
:
pass
簡而言之,
@new_decorated_function
就是將
original_function()
,并返回新的
original_function = new_decorated_function(original_function)
實現(xiàn)裝飾功能
這里為了更好地理解裝飾器到底做了什么,我們舉點例子:
def
make_pretty
(
func
)
:
def
inner
(
)
:
print
(
"I got decorated"
)
func
(
)
return
inner
def
ordinary
(
)
:
print
(
"I am ordinary"
)
上面定義了一個簡單的閉包函數(shù)以及一個普通函數(shù),接下來我們要做的事情就是裝飾
oridinary()
函數(shù)
>>
>
ordinary
(
)
I am ordinary
>>
>
# let's decorate this ordinary function
>>
>
pretty
=
make_pretty
(
ordinary
)
>>
>
pretty
(
)
I got decorated
I am ordinary
可以看到,原本只能輸出
I am ordinary
的函數(shù),經(jīng)過裝飾后,可以輸出
I got decorated
了!(我這么說好像有點奇怪,你可以理解成原本只能走路的人,突然給你裝上了翅膀可以飛了)。
在這里
make_pretty()
是個裝飾器,在下面的賦值語句中:
pretty
=
make_pretty
(
ordinary
)
ordinary()
被裝飾后,給予了一個新的名字
pretty
,通常,我們裝飾器做了如下工作:
ordinary
=
make_pretty
(
ordinary
)
添加裝飾器
我們可以簡單使用
@
符號,放置于需要裝飾的函數(shù)前來裝飾該函數(shù)。
@make_pretty
def
ordinary
(
)
:
print
(
"I am ordinary"
)
等價于:
def
ordinary
(
)
:
print
(
"I am ordinary"
)
ordinary
=
make_pretty
(
ordinary
)
含參裝飾器
上述的裝飾器十分簡單,并且只能適用于沒有任何參數(shù)的函數(shù),如果我們想要實現(xiàn)的函數(shù)含有如下的參數(shù)呢?
def
divide
(
a
,
b
)
:
return
a
/
b
這是一個簡單的除法函數(shù),有兩個參數(shù)
a
和
b
,我們知道,當(dāng)
b
為
0
的時候該函數(shù)會出錯。
>>
>
divide
(
2
,
5
)
0.4
>>
>
divide
(
2
,
0
)
Traceback
(
most recent call last
)
:
.
.
.
ZeroDivisionError
:
division by zero
現(xiàn)在我們已經(jīng)實現(xiàn)好了
divide
函數(shù),但是我們發(fā)現(xiàn)并沒有對
b=0
做錯誤處理,而我們又不想重構(gòu)代碼,這時候我們只需要簡單寫好一個裝飾器,裝飾該函數(shù)即可。
def
smart_divide
(
func
)
:
def
inner
(
a
,
b
)
:
print
(
"I am going to divide"
,
a
,
"and"
,
b
)
if
b
==
0
:
print
(
"Whoops! cannot divide"
)
return
return
func
(
a
,
b
)
return
inner
@smart_divide
def
divide
(
a
,
b
)
:
return
a
/
b
這里我們實現(xiàn)了一個裝飾器,將原來比較不完美的函數(shù)
divide()
裝飾成了一個新的更加完善的函數(shù)
smart_divide()
>>
>
divide
(
2
,
5
)
I am going to divide
2
and
5
0.4
>>
>
divide
(
2
,
0
)
I am going to divide
2
and
0
Whoops! cannot divide
在這個例子里,我們裝飾了帶參數(shù)的函數(shù)。當(dāng)然,你可能會注意到,裝飾器中的內(nèi)嵌函數(shù)
inner()
與被裝飾函數(shù)的參數(shù)一樣。考慮到這一點,現(xiàn)在我們可以定義一個通用裝飾器,從而可以使用任意數(shù)量的參數(shù)。
Python中,使用的是類似
function(*args, **kwargs)
的實現(xiàn)。其中,
args
是位置參數(shù)的元組,
kwargs
是關(guān)鍵字參數(shù)的字典。一下的裝飾器就是一個例子。
def
works_for_all
(
func
)
:
def
inner
(
*
args
,
**
kwargs
)
:
print
(
"I can decorate any function"
)
return
func
(
*
args
,
**
kwargs
)
return
inner
鏈?zhǔn)窖b飾器
在Python中,我們可以將多個裝飾器“鏈接”起來,也就是,一個函數(shù)可以被多個相同或者不同的裝飾器裝飾,例如:
def
star
(
func
)
:
def
inner
(
*
args
,
**
kwargs
)
:
print
(
"*"
*
30
)
func
(
*
args
,
**
kwargs
)
print
(
"*"
*
30
)
return
inner
def
percent
(
func
)
:
def
inner
(
*
args
,
**
kwargs
)
:
print
(
"%"
*
30
)
func
(
*
args
,
**
kwargs
)
print
(
"%"
*
30
)
return
inner
@star
@percent
def
printer
(
msg
)
:
print
(
msg
)
接下來我們調(diào)用
printer()
函數(shù)
>>
>
printer
(
"Hello"
)
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
Hello
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
上述的語句:
@star
@percent
def
printer
(
msg
)
:
print
(
msg
)
等價于
def
printer
(
msg
)
:
print
(
msg
)
printer
=
star
(
percent
(
printer
)
)
從這里我們可以看到,順序是十分重要的,如果順序交換的話,將會得到不一樣的結(jié)果:
@percent
@star
def
printer
(
msg
)
:
print
(
msg
)
調(diào)用
printer()
函數(shù)
>>
>
printer
(
"Hello"
)
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
Hello
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
%
結(jié)果反過來了~
[1] Python Closures
[2] Python 的閉包和裝飾器
[3] Python Decorators
[4] 理解Python中的閉包
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
