Skip to content

闭包

作用域

  • 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
  • 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  • 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)

作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。

python

# int str input  1. 内建作用域


# 2. 导入的模块 定义的全局变量
import pprint
import sys

# 2. 自己定义的全局变量
number = 100

pprint.pprint(globals())


def func():
    # 局部变量
    nickname = '正心老师'
    print(nickname)


func()

定义在模块最外层的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的。例如:

python
number = 100  # 全局变量


def foo():
    print(number)  # 10

嵌套函数

函数不仅可以定义在模块的最外层,还可以定义在另外一个函数的内部,像这种定义在函数里面的函数称之为嵌套函数(nested function)例如:

python
def print_msg():
    # print_msg 是外围函数
    msg = 10

    def printer():
        # printer是嵌套函数
        print(msg)

    printer()


# 输出
print_msg()

对于嵌套函数,它可以访问到其外层作用域中声明的非局部(non-local)变量,比如代码示例中的变量 msg 可以被嵌套函数 printer 正常访问。

那么有没有一种可能即使脱离了函数本身的作用范围,局部变量还可以被访问得到呢?答案是闭包

python
def func2():
    nickname = 'xxx'

    def local_func():
        # 局部作用的局部作用域
        number = 10
        return number

    return local_func


g_func = func2()
print(g_func())

什么是闭包

闭包就是能够读取其他函数内部变量的函数, 由于在 Python 语言中,只有函数内部的子函数才能读取局部变量, 因此可以把闭包简单理解成 “定义在一个函数内部的函数”。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途:

  • 可以在 函数外部 读取 函数内部成员
  • 函数内成员始终存活在内存中

函数身为第一类对象,它可以作为函数的返回值返回,现在我们来考虑如下的例子:

python
import random


def foo():
    """闭包有自己的环境"""
    num = random.randint(1, 10)

    def printer():
        return num

    return printer


# 把函数当做变量使用
foo1 = foo()
print('foo1', foo1())

foo2 = foo()
print('foo2', foo2())
print('foo2', foo2())

不同的地方在于内部函数 printer 直接作为返回值返回了。

一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 foo() 执行过后,我们会认为 num 变量将不再可用。然而,在这里我们发现 foo 执行完之后,在调用 foo 的时候 num 变量的值就返回了,这就是闭包的作用,闭包使得局部变量在函数外被访问成为可能。

提示

维基百科上的解释是:

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

案例:直线方程配置

使用 python 实现 y = kx + b

  1. 传入不同的 x 可以生成对应的直线方程 yx
  2. 根据对应的直线方程,多次传入不同的 (k,b) 可以得到不同的结果。
python
# -------------------- 直线方程配置 -------------------- #
def line_conf(a, b):
    def line(x):
        print("x =", x)
        return a * x + b

    print("y = {}*x + {}".format(a, b))
    return line


# y = 2*x + 1 
line1 = line_conf(2, 1)
print("y =", line1(5), "\n")

# y = 4*x + 5
line2 = line_conf(4, 5)
print("y =", line2(5), "\n")

global 和 nonlocal 关键字

当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字了。

global 用于声明全局变量, nonlocal 用于闭包向上查找变量。