Python 闭包

简介: Python 闭包

闭包(closure)作为一个不太容易理解的概念出现在很多主流编程语言中,Python 中很多高级实现都离不开 闭包装饰器 就是使用 闭包 的典型例子。

作用域

要学习 闭包,先了解 作用域 的概念。

作用域 是程序运行时变量的存在范围。常见 作用域全局作用域局部作用域,定义在全局作用域中的变量,在程序运行过程中的任何地方都可以访问到;而定义在函数内部的变量只有在函数内部才能够访问,函数内部的作用域就是局部作用域,为了便于理解,我这里称它为函数作用域

全局作用域不可以读取 函数作用域 中的局部变量:

def foo():
    num = 100
print(num)  # NameError: name 'num' is not defined

函数作用域 可以向上读取 全局作用域 中的全局变量:

num = 100
def foo():
    print(num)  # 100
foo()

闭包

在 Python 中,函数是 一等公民,意思是说你可以像看待 intdict 等常见的数据类型一样去看待它。其实函数不仅仅可以定义在 全局作用域 中,函数也可以定义在另一个 函数作用域 中。

def foo():
    num = 100
    def bar():
        print(num)  # 100
    bar()
foo()

以上代码中,在 foo 函数内部又定义了一个 bar 函数。bar 函数可以向上读取 foo 函数内定义的任何变量。bar 函数读取变量 num 的顺序是:先在 bar 函数自己的 函数作用域 内查找,如果找不到就向上查找 foo 函数的 作用域,如果还是找不到,最后再向上查找 全局作用域

以上代码稍作修改,就能成为一个 闭包 函数:

def foo():
    num = 100
    def bar():
        print(num)  # 100
    return bar
result = foo()
result()

可以看到,只需要将之前的示例代码中 bar() 这句函数调用,改为 return bar,然后在调用 foo 函数时用一个变量 result 接收 foo 函数的返回值,最后再调用 result 这个变量,就能得到同样的结果。这样其实间接的的实现了在 全局作用域读取函数作用域 中的变量。这就是 闭包 的威力。

像这样,函数嵌套函数,内部函数引用外部函数 作用域 中的局部变量,外部函数将内部函数的引用当作返回值返回,此时,我们就可以将内部函数和它引用的外部函数 作用域 中的局部变量统称为 闭包

闭包的应用

假如需要实现一个计数器,我们很容易想到使用 来实现:

class Counter(object):
    def __init__(self):
        self.num = 0
    def __call__(self):
        self.num += 1
        return self.num
counter = Counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

由于 可以保存数据并且操作数据,所以很轻松就能够使用 来实现计数器。

而函数本身没法在每次调用时保存数据,所以无法实现一个计数器的功能。但当我们有了 闭包 函数,就能够用 函数 的形式来实现计数器了。

def make_counter():
    num = 0
    def counter():
        nonlocal num
        num += 1
        return num
    return counter
counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

以上,我们就用 闭包函数 实现了一个计数器。

可以看到内部 counter 函数有一条语句 nonlocal num,关键字 nonlocal 的作用可以对应 global 关键字来理解。当我们在 函数作用域 内部修改 全局作用域 中的 不可变类型 变量时,我们会使用 global 关键字来标明一个变量是全局变量,同样的 nonlocal 关键字的作用是标明 num 是一个 闭包 中的变量,它有一个专业的术语叫做 自由变量。通常来说,一个函数执行完成以后,其内部的变量会随之被销毁,而 自由变量num 并不会被立即销毁,它随同 counter 函数一起组成了 闭包

闭包的陷阱

下面是一个使用 闭包 时常见的误区:

def func():
    li = []
    for i in range(3):
        def f():
            return i
        li.append(f)
    return li
li = func()
for f in li:
    print(f())
# 运行结果
# 2
# 2
# 2

我们期待的打印结果依次为 012,但实际上运行以上代码,得到的结果确是 222

这是因为,内部函数 f 和循环变量 i 形成了 闭包func 函数内部共产生了 3 个 f 函数,它们依次被追加到 li 列表中,但是并没有立即被执行,而其内部引用了 自由变量i,在 3 次 for 循环执行完成后,i 的值已经变成了 2,等到在函数外部接收到 li 再一次循环执行每个 f 函数时,所打印的 i 的值自然就都为 2 了。

要解决这个问题,需要想办法在每次循环时,让内部的 闭包 函数 f 绑定住当前的循环变量,而不使其跟随 i 的变动而改变:

def func():
    li = []
    for i in range(3):
        def wrap(j):
            def f():
                return j
            return f
        li.append(wrap(i))
    return li
li = func()
for f in li:
    print(f())
# 运行结果
# 0
# 1
# 2

在这里,我在内部函数 f 的外部又嵌套了一层函数 wrap,在执行 li.append(wrap(i)) 语句时,将循环变量 i 的值传递给 wrap 函数,这样 wrap 函数执行完成后,函数 f 和变量 j 就组成了 闭包。每次 for 循环时,都将循环变量 i 当作参数传递给 wrap 函数,由于每产生的 wrap 函数相互独立并且绑定到函数参数上的变量值不会改变,这样就能保证每次循环过程中在 wrap 函数内部保存的变量 j 互相独立,所以最终能够得到预期的结果。

相关文章
|
1月前
|
Python
闭包(Closure)是**Python中的一种高级特性
闭包(Closure)是**Python中的一种高级特性
44 8
|
2月前
|
存储 缓存 算法
Python闭包|你应该知道的常见用例(下)
Python闭包|你应该知道的常见用例(下)
32 1
Python闭包|你应该知道的常见用例(下)
|
2月前
|
自然语言处理 小程序 测试技术
Python闭包|你应该知道的常见用例(上)
Python闭包|你应该知道的常见用例(上)
34 3
Python闭包|你应该知道的常见用例(上)
|
6月前
|
监控 测试技术 Python
颠覆传统!Python闭包与装饰器的高级实战技巧,让你的项目效率翻倍
【7月更文挑战第7天】Python的闭包与装饰器是强大的工具。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建和工厂模式。例如,`make_power`返回含外部变量`n`的`power`闭包。装饰器则允许在不修改函数代码的情况下添加新功能,如日志或性能监控。`my_decorator`函数接收一个函数并返回包装后的函数,添加了前后处理逻辑。掌握这两者,可提升编程效率和灵活性。
49 3
|
3月前
|
Python
深入理解Python中的闭包
深入理解Python中的闭包
46 0
|
6月前
|
存储 安全 Java
在python中使用闭包和其他惯例
【7月更文挑战第3天】本文介绍闭包基本概念和例子,内部函数访问外部变量,实现数据隐藏。以及 Python的惯用法:用`in`检查字典键,用`dict.get()`安全取值。
55 1
在python中使用闭包和其他惯例
|
5月前
|
数据安全/隐私保护 Python
Python闭包:函数定义的神秘力量!
Python闭包:函数定义的神秘力量!
66 0
|
6月前
|
程序员 Python
从零到一,彻底掌握Python闭包与装饰器的精髓,成为编程界的隐藏Boss
【7月更文挑战第7天】探索Python编程的两大基石:闭包与装饰器。闭包是内部函数记住外部作用域的变量,如`make_multiplier_of`返回的`multiplier`,它保持对`n`的引用。装饰器则是函数工厂,接收函数并返回新函数,如`my_decorator`,它在不改变原函数代码的情况下添加日志功能。掌握这些,让代码更优雅,效率更高,助你成为编程高手。
40 3
|
6月前
|
程序员 Python
程序员必看!Python闭包与装饰器的高级应用,让你的代码更优雅、更强大
【7月更文挑战第7天】Python中的闭包和装饰器是高级特性,用于增强代码功能。闭包是内部函数记住外部作用域的变量,常用于动态函数和函数工厂。示例展示了`make_multiplier_of`返回记住n值的`multiplier`闭包。装饰器则是接收函数并返回新函数的函数,用于不修改原函数代码就添加功能。`my_decorator`装饰器通过`@`语法应用到`say_hello`函数上,展示了在调用前后添加额外行为的能力。这两种技术能提升代码的优雅性和效率。
47 3
|
6月前
|
Python
Python编程实战:利用闭包与装饰器优化日志记录功能
【7月更文挑战第7天】Python的闭包和装饰器简化了日志记录。通过定义如`log_decorator`的装饰器,可以在不修改原函数代码的情况下添加日志功能。当@log_decorator用于`add(x, y)`函数时,调用时自动记录日志。进一步,`timestamp_log_decorator`展示了如何创建特定功能的装饰器,如添加时间戳。这些技术减少了代码冗余,提高了代码的可维护性。
79 1