闭包

浏览 1758

课文

在 python 里万物皆对象,函数也不例外,于是乎我们可以在一个函数中定义另一个函数作为返回值。

def func():
    def wrapper():
        print('你好')

    return wrapper

result= func()
result()
你好

这就是一个闭包(closure)了吗?还少了点味。

因为根据维基百科上对闭包的定义:

在支持头等函数的语言中,如果函数f内定义了函数g,那么如果g存在自由变量,且这些自由变量没有在编译过程中被优化掉,那么将产生闭包。

人话: f 函数中定义了 g 函数, g 还引用了 f 定义的变量, 就是闭包。

代码:

def func(name):
    def wrapper():
        print(f'你好,{name}')

    return wrapper

result = func('小明')
result()

由作用域的查找规则可知,在这段代码中,func 函数内是局部作用域, wrapper 函数内是闭合作用域, wrapper 函数可以直接使用 func 函数内的变量。

那 python 是怎么实现闭包的呢?

在实现闭包时,返回的函数会带有 __closure __ 属性,在__closure __ 里可以找到函数所需要的变量。

print(result.__closure__) # 打印一下 __closure__ 属性
print(result.__closure__[0].cell_contents) # 通过 cell_contents 可以得到保存的变量
(<cell at 0x000001DF6F6B86D0: str object at 0x000001DF6F6B5C30>,)
小明

闭包的应用场景

使用闭包主要有以下三个好处。

  • 关联数据与函数
  • 保存临时变量,减少全局变量
  • 保存私有变量,防止变量被修改

在 python 中闭包有个很经典的应用场景就是装饰器,放在下节讲。

关联数据与函数

关联数据与函数,这与面向对象中很类似,但有时只有一个方法时,使用闭包会简单很多。

# 写法 1:用类实现
class Timer:
    def __init__(self, base):
        self.base = base

    def time(self, x):
        return self.base * x

timer2 = Timer(2)
print('结果:', timer2.time(4))

# 写法 2:用闭包实现
def timer(base):
    def wrapper(x):
        return x * base

    return wrapper

timer2 = timer(2)
print('结果:', timer2(4))
结果: 8
结果: 8

以上两种方式,用闭包实现会简单很多。

保存临时变量

由于闭包是把变量保存在局部作用域中,所以可以减少使用全局变量。

 # 写法 1:将变量定义在全局作用域
count = 0

def func():
    global count
    count += 1
    print(f'执行了{count}次')

func()
func()

# 写法 2:将变量定义在闭包中的局部作用域
def counter():
    count = 0

    def wrapper():
        nonlocal count
        count += 1
        print(f'执行了{count}次')

    return wrapper

func = counter()
func()
func()
执行了1次
执行了2次
执行了1次
执行了2次

保存私有变量

闭包中传入的变量只能被内部的函数修改,这使得我们可能通过这个特性创建一个防止被修改的字典。

def my_dict(**kwargs):
    def wrapper():
        # 这里是重新生成了一个字典对象,
        # 因为如果直接返回则属于浅拷贝,
        # 会导致外部仍能修改字典的值。
        return {**kwargs}

    return wrapper

xiaoming = my_dict(name='小明', age=18)

print('修改前:', xiaoming())
xiaoming()['name'] = '小红'
print('修改后:', xiaoming())
修改前: {'name': '小明', 'age': 18}
修改后: {'name': '小明', 'age': 18}

成功避免了被更名改姓。

评论

登录参与讨论

暂无评论

共 0 条
  • 1
前往
  • 1

课程目录

第一课:命名空间与作用域 第二课:闭包 第三课:装饰器 第四课:迭代器 第五课:生成器

学习遇到困难?微信扫码进入社群与小伙伴一起交流讨论。