文章目录
- 闭包(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中的闭包