浅谈python闭包及装饰器

1. 什么是闭包:

  闭包 是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。 

 

2. 形成闭包的条件:

  1. 该函数体的整体类型为高阶函数(解释:一个函数可以作为参数传给另外一个函数,或者一个函数的返回值为另外一个函数(若返回值为该函数本身,则为递归),满足其一则为高阶函数)

  2. 内部函数引用了*变量(该变量可以是外部函数的形参, 也可以是外部函数的局部变量)

  通俗来说就是定义在某一个函数内部的函数

3. 实现

  1. 下面来举一个简单的闭包(不修改变量outer_var,只做读取)例子:

def outer():
    outer_var = 1
    def inner():
        inner_var = outer_var + 100
        print('inner:	%s' %  inner_var)
    return inner

  执行结果如下:  

  浅谈python闭包及装饰器

  但是从变量的生存周期来看,该怎么理解呢?我们的变量outer_var是函数outer的一个本地变量,这意味着只有当函数outer正在运行的时候才会存在。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在函数inner被调用的时候,变量outer_var早已不复存在,可能会发生一个运行时错误。然而返回的函数inner居然能够正常工作。Python支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如outer_var,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)记住,每次函数outer被调用的时候,函数inner都会被重新定义。现在变量outer_var的值不会变化,所以每次返回的函数inner会是同样的逻辑

  2. 通过子函数修改父函数的*变量

  在实现该功能之前不得不提及两个关键字, global及nonlocal

# global

GLOABL_BASE = 100

def increaseNum1():
GLOABL_BASE = 99
print(GLOABL_BASE, id(GLOABL_BASE))

def increaseNum2():
global GLOABL_BASE
GLOABL_BASE = 99
print(GLOABL_BASE, id(GLOABL_BASE))

if __name__ == '__main__':
print("*"*30)
increaseNum1()
print(GLOABL_BASE, id(GLOABL_BASE))
print("*" * 30)
increaseNum2()
print(GLOABL_BASE, id(GLOABL_BASE))

浅谈python闭包及装饰器

可以发现, 函数体内部的GLOBAL_BASE为该函数内部的局部变量, 模块级别下的GLOBAL_BASE与该函数作用域不产生共享,如果将函数内的变量声明为模块级别, 那么就实现了修改全局变量的功能

  那么在闭包环境下,如果修改外部函数中的变量呢?

def outer(init_num):
    print("outer:	{0}".format(init_num))
    def inner(step):
        nonlocal init_num
        init_num += step
        print("inner:	{0}".format(init_num))
    return inner

if __name__ == '__main__':
    func = outer(0)
    for step in range(3):
        func(2)
    print("*"*30)
    for step in range(3):
        outer(0)(2)
    print("*" * 30)

浅谈python闭包及装饰器

解释: 修改变量的作用域级别实现了, 但为什么运行期间保留了父函数中的变量值,这正是闭包,父函数会等待子函数调用彻底结束时才会释放空间,这里的func对象的生命周期直到最后一次被执行(函数对象同样具备__call__方法)才释放。

3. 剖析相关概念

  1. 作用域:我们知道对于一个模块来说, 其可能包含函数、类等抽象结构, 宏观上来看, 一个模块划分了多个作用域, 且是层层包含的关系, 那么在一个函数内部的命名空间内当出现一个变量的引用时,他会优先在自己的命名空间内查找该变量,这里我们就以全局作用域以及局部作用域为例,来做一下验证

# -*- coding:utf-8 -*-
"""测试作用域"""
GLO = 100
def local_func():
    LOC = 1
    print(locals())

def main():
    for k, v in globals().items():
        print(k,v)
    print("*"*30)
    local_func()

if __name__ == '__main__':
    main()

浅谈python闭包及装饰器

  2. 

  

4. 闭包的内存模型