当前位置: 首页 > news >正文

Python 闭包(Closure)实战总结

文章目录

    • 一、什么是闭包?一句话说清
    • 二、闭包的作用和使用场景
      • 1. 数据封装与状态保持(替代简单的类)
      • 2. 实现装饰器
      • 3. 函数工厂
    • 三、`nonlocal` 关键字的重要性
    • 四、一个常见的“坑”:循环中的闭包
    • 总结

在日常的 Python 开发中,我们经常会谈到装饰器、回调函数、函数式编程等概念,而它们背后都离不开一个核心的特性—— 闭包(Closure)。起初,闭包可能听起来有些抽象,但一旦理解了它的本质,你会发现它是一个非常强大且优雅的工具,能让你的代码更加简洁和高效。

这篇文章是我在工作中对闭包的一些理解和应用总结,希望能帮助你彻底搞懂它。

一、什么是闭包?一句话说清

抛开复杂的定义,我们可以这样理解:

闭包就是一个“记住”了自己创建时所在环境的函数。

即便创建它的外部函数已经执行完毕,闭包依然可以访问和操作那些外部函数中的变量。

要构成一个闭包,必须满足以下三个条件:

  1. 函数嵌套:存在一个外部函数,内部定义了另一个函数。
  2. 内部函数引用外部变量:内部函数必须引用了外部函数作用域中的变量(非全局变量)。
  3. 外部函数返回内部函数:外部函数最终返回的是内部函数对象,而不是调用它。

让我们来看一个最经典的例子:

def outer_func(x):# x 是外部函数的局部变量,也叫“自由变量”def inner_func(y):# inner_func 引用了外部的变量 xreturn x + y# 返回内部函数对象return inner_func# 调用外部函数,得到一个闭包
add_5 = outer_func(5)
add_10 = outer_func(10)# 执行闭包
print(f"add_5(1) 的结果是: {add_5(1)}")  # 输出: 6
print(f"add_10(2) 的结果是: {add_10(2)}") # 输出: 12

在这个例子中,add_5add_10 就是闭包。当 outer_func(5) 执行完毕后,变量 x=5 并没有消失,而是被“绑定”到了 add_5 这个函数上。

我们可以通过 __closure__ 这个特殊属性来验证,它会告诉你闭包捕获了哪些自由变量:

print(add_5.__closure__)
# (<cell at 0x...: int object at 0x...>,)
print(add_5.__closure__[0].cell_contents)
# 5

二、闭包的作用和使用场景

理解了概念,那么闭包在实际工作中有什么用呢?

1. 数据封装与状态保持(替代简单的类)

闭包提供了一种轻量级的方式来封装数据和行为,避免使用全局变量,同时又不像定义一个完整的类那么“重”。

想象一下,你需要一个计数器:

def make_counter():count = 0  # 这个状态被封装在闭包中def counter():nonlocal count # 声明 count 不是局部变量count += 1return countreturn countercounter1 = make_counter()
print(counter1())  # 1
print(counter1())  # 2counter2 = make_counter() # 创建一个新的、独立状态的计数器
print(counter2())  # 1

count 变量被安全地隐藏在 counter 函数的作用域内,外部无法直接访问,只能通过调用 counter1() 来修改。这实现了一种优雅的状态保持。

2. 实现装饰器

这是闭包最广为人知的应用。Python 中的装饰器本质上就是一个接受函数并返回一个新函数的闭包。

import timedef timer_decorator(func):# 这是一个典型的闭包结构def wrapper(*args, **kwargs):start_time = time.time()result = func(*args, **kwargs)end_time = time.time()print(f"函数 '{func.__name__}' 执行耗时: {end_time - start_time:.4f} 秒")return resultreturn wrapper@timer_decorator
def some_task(n):time.sleep(n)print("任务完成!")some_task(2)

@timer_decorator 只是 some_task = timer_decorator(some_task) 的语法糖。wrapper 函数就是一个闭包,它“记住”了 func(也就是原始的 some_task 函数),并在其基础上添加了计时功能。

3. 函数工厂

闭包非常适合用来创建一系列功能相似但配置不同的函数。

def make_multiplier(n):"""创建一个乘以n的函数"""def multiplier(x):return x * nreturn multiplier# 函数工厂 "生产" 出不同的函数
times_3 = make_multiplier(3)
times_5 = make_multiplier(5)print(times_3(10))  # 30
print(times_5(10))  # 50

三、nonlocal 关键字的重要性

make_counter 的例子中,我们用到了 nonlocal。这是一个关键点。

如果我们在闭包内部尝试修改一个捕获的变量,Python 默认会认为我们正在创建一个新的局部变量。

def make_counter_wrong():count = 0def counter():# 如果没有 nonlocal,下面这行会报错 UnboundLocalError# 因为 Python 认为你在给一个不存在的局部变量 count 赋值count += 1return countreturn counter

nonlocal 关键字就是用来告诉 Python:“嘿,我不是要创建新变量,我是要修改外层(但非全局)作用域里的那个 count 变量。”

  • global:修改全局作用域的变量。
  • nonlocal:修改外层非全局作用域的变量。

四、一个常见的“坑”:循环中的闭包

这是一个经典的面试题,也是实际开发中可能遇到的问题。

funcs = []
for i in range(3):def create_func():return i * ifuncs.append(create_func)# 你期望的输出可能是 0, 1, 4
# 但实际输出是...
for f in funcs:print(f())# 输出:
# 4
# 4
# 4

为什么会这样?

因为闭包捕获的是变量 i 本身,而不是它在循环中某一刻的值。当循环结束后,i 的值最终变成了 2。所以,后续调用所有闭包时,它们访问到的 i 都是 2,结果自然都是 2*2=4。这被称为**“延迟绑定”或“后期绑定”**。

如何修复?

思路是让闭包在创建时就“固定”住当时的值。最简单的方法是利用函数默认参数:

funcs_fixed = []
for i in range(3):# 利用默认参数在定义时就绑定 i 的值def create_func_fixed(val=i):return val * valfuncs_fixed.append(create_func_fixed)for f in funcs_fixed:print(f())# 输出:
# 0
# 1
# 4

总结

闭包是 Python 中一个强大而基础的概念。它不仅是理解装饰器等高级功能的基石,本身也是编写干净、模块化代码的利器。

  • 核心:函数和其创建时环境的结合体。
  • 优点:数据封装、状态保持、避免污染全局命名空间。
  • 应用:实现装饰器、函数工厂、轻量级状态机。
  • 注意:修改捕获变量时使用 nonlocal,警惕循环中的延迟绑定问题。

掌握了闭包,你对 Python 函数式编程的理解会更上一层楼。

http://www.lryc.cn/news/579839.html

相关文章:

  • 万勋科技「柔韧机器人玻璃幕墙清洗」全国巡展@上海!引领清洗无人机智能化升级
  • 读商战数据挖掘:你需要了解的数据科学与分析思维05拟合数据
  • Windows系统下WSL从C盘迁移方案
  • Vue-19-前端框架Vue之应用基础组件通信(二)
  • 算法学习笔记:6.深度优先搜索算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
  • 【办公类-54-07】20250901 2025学年第一学期班级点名册模版(双休国定假涂成灰色、修改标题和页眉,批量导出PDF)
  • 使用alist+RaiDrive+webdav将百度夸克网盘变为本地电脑磁盘方法教程
  • 基于微信小程序的校园二手交易平台、微信小程序校园二手商城源代码+数据库+使用说明,layui+微信小程序+Spring Boot
  • 如何搭建 OLAP 系统?OLAP与数据仓库有什么关系?
  • 推荐算法系统系列>推荐数据仓库集市的ETL数据处理
  • BLDC电机-运动控制---stm32时钟树定时器SYSTICKRTC的学习
  • Django Channels WebSocket实时通信实战:从聊天功能到消息推送
  • 前端查询条件加密传输方案(SM2加解密)
  • 浏览器(Chrome /Edge)高效使用 - 内部命令/快捷键/启动参数
  • 服务器如何配置防火墙规则以阻止恶意流量和DDoS攻击?
  • mybatisPlus分页方言设置错误问题 mybatisPlus对于Oceanbase的Oracle租户分页识别错误
  • HarmonyOS免密认证方案 助力应用登录安全升级
  • 使用循环抵消算法求解最小费用流问题
  • Python 制作 pyd(Windows 平台的动态链接库)
  • 【行云流水ai笔记】粗粒度控制:推荐CTRL、GeDi 细粒度/多属性控制:推荐TOLE、GPT-4RL
  • 10分钟搭建 PHP 开发环境教程
  • Java对象哈希值深度解析
  • 支持向量机(SVM)在LIDC-IDRI数据集上的多分类实现(肺癌检测)
  • 三五法则的类的设计
  • 供应链管理:指标评估方式分类与详解
  • Rust 中的返回类型
  • 云原生Kubernetes系列 | etcd3.5集群部署和使用
  • Day51 复习日-模型改进
  • TCP、HTTP/1.1 和HTTP/2 协议
  • 怎么更改cursor字体大小