Python之闭包、装饰器

什么是闭包

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。 上面提到了两个关键的地方: 自由变量 和 函数, 这两个关键稍后再说。还是得在赘述下“闭包”的意思,望文知意,可以形象的把它理解为一个封闭的包裹,这个包裹就是一个函数,当然还有函数内部对应的逻辑,包裹里面的东西就是自由变量,自由变量可以在随着包裹到处游荡。当然还得有个前提,这个包裹是被创建出来的。

再通过Python的语言介绍一下,一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量。 举个栗子:

1
2
3
4
5
6
7
8
9
def fa(n):
    num = 1
    def fb():
        return n+num
    return fb
# 测试代码:
f = fa(1)
print("f不是直接等于2,而是一个函数,f:", f)
print("f()返回结果才等于2,f():", f())

调用fa的时候就产生了一个闭包fb,并且该闭包持有自由变量num,因此这意味着,当函数func的生命周期结束之后,name这个变量依旧存在,因为他被闭包引用了,所以不会回收。

举例闭包的使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def func_150(val):   # 总分为150时,及格分数为90
    passline = 90
    if val >= passline:
        print "pass"
    else:
        print "fail"

def func_100(val):   # 总分为100时,及格分数为60
    passline = 60 
    if val >= passline:
        print "pass"
    else:
        print "fail"

func_100(89)  
func_150(89)
使用闭包优化上面的代码

def set_passline(passline):
    def cmp(val):
        if val >= passline:
            print "pass"
        else:
            print "fail"
    return cmp

f_100 = set_passline(60)    # f_100调用函数set_passline(),并将60赋值给变量passline,这是f_100等于函数的返回值,也就是函数cmp
f_150 = set_passline(90)
f_100(89)     # f_100()=cmp(),将89赋值给val,运行cmp()函数,输出结果
f_150(89)

nonlocal语句

在python的函数内,可以直接引用外部变量,但是不能改写外部变量,因此如果在闭包中直接改写父函数的变量,就会发生错误,例如:

1
2
3
4
5
6
7
8
9
10
def fa(n):
    num = 1

    def fb():
        num = num+1 # 这里会出错! 改写了外部变量!
        return n+num
    return fb

# 测试代码:
f = fa(1)

在python2中可以在函数内使用global语句,但是全局变量在任何语言中都不被提倡,因为它很难控制,python3中引入了nonlocal语句来解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
def fa(n):
    num = 1

    def fb():
        nonlocal num  # 声明了nonlocal不会报错了
        num = num+1 
        return n+num
    return fb

# 测试代码:
f = fa(1)

nonlocal与global的区别在与nonlocal语句回去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。

装饰器模式

装饰器是函数闭包的一种应用。装饰器函数用来装饰一般函数(给一个函数增加额外的功能),然后返回新的函数对象给函数本身。具体还是看代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def decorator(func): # 定义装饰器函数,输出函数的开始时间和结束时间
    def after_decor():
        print(func.__name__, "start:")
        func()
        print( func.__name__, "end...")
    return after_decor

@decorator
def bark():  # 定义bark函数,然后使用decorator函数来装饰他
    for i in range(3):
        print("Wooo~")
        time.sleep(1)

# 测试代码
bark()  # 调用bark函数

运行结果:

1
2
3
4
5
bark start:
Wooo~
Wooo~
Wooo~
bark end...

闭包的作用

闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在,这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活

闭包可以避免全局变量的使用。

对闭包的理解还是需要多多实践,多多体会。

本文参考: http://www.mylanbitou.top/blog/detail/3/#Coon