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

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