Python教程 - 闭包

301 阅读3分钟

本篇文章将介绍Python中的“闭包(Closure)”,进一步理解作用域(scope)和自由变量的概念。

什么是闭包closure

闭包,从字面的意思翻译就是一个“封闭的包裹”,在包裹外的人,无法拿到包裹里的东西,如果你在包裹里,就能尽情使用包裹内的东西。闭包可以保存在函数作用范围内的状态,不会受到其他函数的影响,且无法从其他函数获取闭包内的数据,也可以避免建立许多全局变量相互干扰。

闭包的定义:

  • 一个函数中定义了另一个函数。

  • 被定义的函数使用了原函数的变量。

  • 原函数返回了被定义的函数。

下方的代码,就是一个简单的闭包示例。

def a(msg):
    i = '!!!'  # 闭包开始
    def b():   # A函数内定义了B函数
        print(msg + i)  # B函数使用了A函数的变量
    return b  # 将B函数作为返回值,闭包结束

s = a('hello')
s()  # 输出 hello

什么是作用域scope

作用域Scope指的是变量、常量、函数或其他定义语句可以“被访问到”的范围。Python共定义了四种作用域,由内而外分别是局部(Local)、闭包外函数(Enclosing)、全局(Global)和内置(Builtin)。内部的作用域无法影响到外部作用域。

图片

闭包的示例应用

下方的代码,会建立一个avg函数的闭包,执行后虽然test()执行了三次,但因为每次执行时保留了一个作用域的绑定关系,所以会不断将传入的数值进行计算,最后就会得到11的结果。

def count():  # 建立一个count函数
    a = []  # 函数内有局部变量a是列表
    def avg(val):  # 建立内嵌函数avg(闭包)
        a.append(val)  # 将参数数值加入变量a
        print(a)  # 打印a
        return sum(a)/len(a)  # 返回a列表所有数值的平均值
    return avg  # 返回avg

test = count()
test(10)  # 将10存入a
test(11)  # 将11存入a
test(12)  # 打印11

图片

自由变量nonlocal

不过如果将上方的例子,改成变量的做法,可能会发生错误,因为在cal函数里的变量a后方使用了“等号”,意义等同于变量的“赋值”,换句话说是新建了一个局部变量a,就造成了命名空间里名称重叠的问题。

def count():
    a = 1  # 新增变量a等于1
    def cal(val):  # 建立内嵌函数cal(闭包)
        a = a + val  # 设定变量a等于a加val
        return a  # 返回a
    return cal  # 返回cal

test = count()
test(10)
test(11)

图片

如果必须这么做,可以使用nonlocal的方式,声明这个变量是“自由变量”(不是这个区域内的变量),就能正常使用这个变量。

def count():
    a = 1
    def cal(val):
        nonlocal a  # 声明a为自由变量
        a = a + val
        return a
    return cal

test = count()
test(10)
test(11)
test(12)  # 输出34(1 + 10 + 11 + 12)