Python从入门到精通(第11章):函数进阶:作用域与闭包

news2026/3/29 8:37:48
Python从入门到精通第11章函数进阶作用域与闭包开头导语这是本系列第11章。前面你已经掌握函数的基本定义和调用方式这一章在此基础上向前一步解决三个实际问题变量名冲突时 Python 到底用了哪一个、为什么内部函数能够“记住”外部函数的变量、闭包在真实业务里能做什么。阅读时建议边看边动手改代码尤其要关注“错误示例”部分——这些错误在后续阅读他人代码时几乎一定会遇到。章节摘要本章围绕“作用域”与“闭包”两条主线展开。作用域决定了一个名字在哪个范围内有效Python 按 LEGB 顺序查找变量闭包则是函数作为返回值时把外部变量“携带”出来的机制常用于状态保持和函数生成。掌握这两点之后你读别人代码时不会再被“变量到底用的是哪个”这类问题困扰写自己的代码时也能设计出更干净的接口。关键词LEGBglobalnonlocal闭包嵌套函数函数工厂状态保持学习目标能用 LEGB 规则解释任意一行代码里变量的来源能区分局部变量和全局变量知道什么情况下必须用 global/nonlocal能看懂闭包的运行机制能自己写一个闭包函数能把“状态保持”需求迁移到闭包实现而不是用全局变量凑合先修知识会定义函数、传递参数、设置默认参数知道列表、字典等可变对象的基本操作理解 if/for/while 的缩进含义环境准备python--versionpython-mvenv .venv# Windows PowerShell.venv\Scripts\Activate.ps1核心知识讲解知识点1LEGB 规则Python 查找一个变量名时按以下顺序逐层搜索LLocal当前函数内部定义的变量EEnclosing外层函数的变量嵌套函数场景GGlobal模块顶层函数外部定义的变量BBuilt-inPython 内置名字如len、print、list下面这段代码演示了一次完整的 LEGB 查找过程bglobal bdefouter():eenclosing edefinner():llocal lprint(l)# L 层找到local lprint(e)# E 层找到enclosing eprint(b)# G 层找到global bprint(len)# B 层找到内置函数 leninner()outer()运行输出local l enclosing e global b built-in function len这个顺序是固定的无论变量名是否相同Python 都按 LEGB 顺序查找不会在找完 L 层之后继续向上找。知识点2局部变量与 global 声明函数内部赋值的变量默认是局部的和函数外部同名变量没有任何关系。错误示例不要这样写count0definc():# 期望对全局 count 1但实际上这里是在创建局部变量 countcountcount1# UnboundLocalError: cannot assign to global variableinc()print(count)问题说明count count 1在 Python 里是一个赋值语句Python 解释器在看到左侧的count时在函数开头就把count作为局部变量处理了所以右侧的count 1去找局部变量但它尚未赋值于是报错。正确写法如果要修改全局变量count0definc():globalcount# 显式声明下面的 count 引用全局变量countcount1inc()print(count)# 输出 1正确写法更推荐的方式通过参数传入、返回值传出count0definc(value):returnvalue1countinc(count)print(count)# 输出 1全局变量看似方便但会导致函数行为依赖外部状态测试困难、难以复用。除非真的需要跨模块共享否则优先通过参数和返回值传递数据。知识点3nonlocal 声明当嵌套函数需要修改外层函数的变量时需要用nonlocal声明。和global不同nonlocal不会穿透到模块顶层只作用于外层函数。错误示例不要这样写defcounter():count0definc():countcount1# 同样是 UnboundLocalErrorreturncountreturninc ccounter()print(c())# 报错问题说明内层函数inc在看到count count 1时把count当作局部变量处理但右侧的count 1在函数执行时还没有值所以报错。正确写法defcounter():count0definc():nonlocalcount countcount1returncountreturninc ccounter()print(c())# 1print(c())# 2print(c())# 3这里inc是一个闭包函数它定义在counter内部但返回后仍然保留对counter的count变量的引用。正是nonlocal count让修改成为可能。知识点4闭包的定义与运行机制闭包Closure是指一个函数记住创建时所在作用域的变量的能力。Python 中只要函数引用了外层作用域的变量Python 就会把被引用的变量绑定到函数对象上这称为“闭包”。下面用图示说明闭包的形成过程counter() 调用 ├── 创建局部变量 count 0 ├── 定义 inc 函数此时 inc 捕获了 count 的引用 └── 返回 inc 函数对象 后续 c() 调用 ├── c 是一个函数对象 ├── c.__closure__ 包含对原 count 单元的引用 └── 每次调用 c() 都修改同一个 count验证闭包是否真正生效defcounter():count0definc():nonlocalcount count1returncountreturninc ccounter()print(c.__closure__)# (cell at ...,) 有值说明是闭包print(c.__code__.co_freevars)# (count,) 捕获的外层变量名print(c())# 1print(c())# 2__closure__不为None是判断闭包是否生效的直接依据。如果__closure__为None说明函数没有捕获外层变量不构成闭包。知识点5闭包的实际用途——函数工厂闭包最常见的用途是生成“带不同初始参数的专业函数”。在没有类的情况下闭包提供了一种轻量级的状态保持方式。错误示例不要这样写用全局变量模拟“配置”base_urlhttps://api.example.compath/usersdefmake_request():returnbase_urlpath# base_url 和 path 是全局变量容易被其他代码污染# 如果需要另一组配置只能再定义新函数或改全局变量base_urlhttps://api2.example.com# 影响之前创建的 make_request问题说明全局变量在程序任何位置都可以被修改多个函数依赖同一全局变量时修改一个会影响另一个难以追踪。正确写法用闭包生成函数工厂defmake_api_client(base_url):path/usersdefmake_request(endpoint):returnf{base_url}{path}{endpoint}defset_path(new_path):nonlocalpath pathnew_pathreturnmake_request,set_path# 生成两个不同配置的客户端req1,set_path1make_api_client(https://api1.example.com)req2,set_path2make_api_client(https://api2.example.com)print(req1(/list))# https://api1.example.com/users/listprint(req2(/list))# https://api2.example.com/users/list# 两个客户端互不影响set_path1(/admin)print(req1(/list))# https://api1.example.com/admin/listprint(req2(/list))# https://api2.example.com/users/list不受影响这里make_api_client是一个函数工厂每次调用都生成一组相互独立的make_request和set_path函数两个客户端之间完全隔离互不干扰。知识点6 nonlocal 与 global 的区别两者名字相似作用域层级不同关键字作用层级影响范围global x模块顶层整个模块文件内有效nonlocal x外层函数仅外层函数作用域不穿透到模块顶层xglobal xdefouter():xouter xdefinner_global():globalx# 修改模块顶层的 xxmodified global xdefinner_nonlocal():nonlocalx# 修改 outer 的 xxmodified outer xprint(fbefore global:{x})# outer xinner_global()print(fafter global:{x})# outer xouter 未变因为改的是全局print(fbefore nonlocal:{x})# outer xinner_nonlocal()print(fafter nonlocal:{x})# modified outer xouter()print(fmodule level:{x})# modified global x实际工程里nonlocal比global少见但在装饰器和嵌套回调里会用到。优先通过返回值传递数据其次nonlocalglobal只在模块级配置常量时使用。案例实战主案例带记忆功能的线性回归器本案例目标用闭包实现一个简易的“带记忆的线性回归器”。需求是每次输入一个新数据点模型记录下来可以查询当前累计了多少个数据点可以清除所有历史数据重新开始不同模型之间数据完全隔离互不影响defmake_regressor():返回一个带记忆的线性回归计算器data_x[]data_y[]defadd_point(x:float,y:float):添加一个数据点nonlocaldata_x,data_y data_x.append(x)data_y.append(y)defmean(values:list)-float:returnsum(values)/len(values)ifvalueselse0.0defslope()-float:计算斜率 b Σ(x-x̄)(y-ȳ) / Σ(x-x̄)²iflen(data_x)2:return0.0x_meanmean(data_x)y_meanmean(data_y)numeratorsum((x-x_mean)*(y-y_mean)forx,yinzip(data_x,data_y))denominatorsum((x-x_mean)**2forxindata_x)returnnumerator/denominatorifdenominator!0else0.0defintercept()-float:计算截距 ā ȳ - b * x̄returnmean(data_y)-slope()*mean(data_x)defpredict(x:float)-float:根据当前数据拟合直线返回预测值returnintercept()slope()*xdefcount()-int:返回累计数据点数量returnlen(data_x)defreset():清除所有历史数据nonlocaldata_x,data_y data_x[]data_y[]defdescribe():return{n:len(data_x),slope:slope(),intercept:intercept(),}returnadd_point,predict,count,reset,describe# 使用方式add1,pred1,cnt1,reset1,desc1make_regressor()add2,pred2,cnt2,reset2,desc2make_regressor()# 模型1 添加数据add1(1.0,2.0)add1(2.0,4.1)add1(3.0,5.9)# 模型2 添加数据独立不受模型1影响add2(1.0,10.0)add2(2.0,20.0)print(desc1())# {n: 3, slope: 1.97, intercept: 0.02}print(desc2())# {n: 2, slope: 10.0, intercept: 0.0}print(cnt1())# 3print(cnt2())# 2print(pred1(4.0))# 用模型1预测 x4 时的 yprint(pred2(4.0))# 用模型2预测 x4 时的 y结果不同# 重置模型1不影响模型2reset1()print(cnt1())# 0print(cnt2())# 2模型2保持不变这个案例展示了闭包的核心价值每个模型的数据data_x、data_y完全隔离不需要用类只通过函数返回值和nonlocal声明就实现了状态管理。模型1和模型2之间没有任何共享变量互相独立。扩展案例ATM 账户模拟器需求用闭包实现一个 ATM 账户具备存钱、取钱、查询余额功能不允许余额为负。defmake_account(initial_balance:float0.0)-tuple:返回一个账户实例存款函数, 取款函数, 查询函数ifinitial_balance0:raiseValueError(初始余额不能为负)balanceinitial_balancedefdeposit(amount:float)-float:nonlocalbalanceifamount0:raiseValueError(存款金额必须为正)balanceamountreturnbalancedefwithdraw(amount:float)-float:nonlocalbalanceifamount0:raiseValueError(取款金额必须为正)ifamountbalance:raiseValueError(f余额不足当前{balance}需取{amount})balance-amountreturnbalancedefget_balance()-float:returnbalancereturndeposit,withdraw,get_balance# 创建两个独立账户dep_a,wit_a,bal_amake_account(1000.0)dep_b,wit_b,bal_bmake_account(500.0)print(bal_a())# 1000.0print(bal_b())# 500.0dep_a(200.0)wit_b(100.0)print(bal_a())# 1200.0print(bal_b())# 400.0try:wit_a(2000.0)# 余额不足应抛异常exceptValueErrorase:print(f取款失败:{e})扩展思考如何在不修改函数签名的情况下给账户加“日累计取款上限”功能提示用闭包再包装一层或者在withdraw函数内部再加一层计数器。常见错误与排查UnboundLocalError函数内部有赋值语句但没有声明global/nonlocal右侧又引用了同名变量。先检查变量是在哪里定义的再决定是用global、nonlocal还是通过参数传入。闭包捕获的是引用不是值如果外层变量是可变对象列表、字典在闭包里可以直接修改不需要nonlocal。闭包函数返回后外层变量消失Python 的垃圾回收机制不会回收仍被闭包引用的外层变量所以闭包是安全的不会产生悬空引用。过度使用 global所有函数都加global会使程序不可测试发现自己频繁使用 global 时优先考虑把数据封装到闭包或类里。性能与工程建议闭包的性能和普通函数几乎没有差别除非在极敏感的热路径上否则不需要担心。闭包适合“函数需要记住一些配置”的场景如果对象有多种方法且需要继承等特性优先用类。每个闭包函数尽量只对应一个明确职责不要在一个闭包里放太多状态。通过返回值显式暴露必要的操作不要把内部变量直接暴露给调用方。本章代码自测清单可打勾我已运行 LEGB 示例代码观察了各层变量的查找顺序。我已复现UnboundLocalError并用global/nonlocal修复。我已打印func.__closure__和func.__code__.co_freevars验证闭包生效。我已写出函数工厂示例理解“生成带不同配置的函数”的过程。我已在 IDE 里逐步调试 ATM 账户案例记录每次余额变化。我已思考扩展练习如何给账户加日累计取款上限。我能解释global和nonlocal的作用域层级差异。我能把本章闭包知识迁移到另一个业务场景如缓存器、计数器、配置隔离。我已完成章末提问至少检查过参考答案。我为下一章整理了待补充的基础清单。章末提问LEGB 分别指哪四个作用域查找顺序是什么什么情况下会产生UnboundLocalError如何修复global和nonlocal的作用范围有什么区别如何判断一个函数是否形成了闭包闭包函数工厂和类有什么相似和不同在 ATM 案例中deposit函数为什么要检查amount 0如果把data_x、data_y从列表换成普通整数闭包还能正常工作吗为什么闭包捕获的是变量的值还是引用这有什么区别在工程中什么情况下会优先选择闭包而不是类如果要消除一个 Python 程序的全局变量闭包能完全替代吗章末答案Local当前函数、Enclosing外层函数、Global模块顶层、Built-in内置。Python 按这个顺序逐层查找找到即停。函数内部对变量赋值且未声明global/nonlocal时Python 把该变量当作局部变量处理但右侧引用时它尚未赋值导致UnboundLocalError。修复方法是加global改模块顶层或nonlocal改外层函数声明或通过参数传入、返回值传出。global把名字绑定到模块顶层的全局变量影响整个文件nonlocal只作用于外层函数不会穿透到模块顶层。检查func.__closure__是否为非None或者检查func.__code__.co_freevars是否包含捕获的变量名。两者都能实现状态保持。类通过实例属性存储状态闭包通过捕获外层变量存储状态。类支持继承和方法多态闭包更轻量适合单一职责。如果需要多种方法且结构复杂优先用类如果只是“生成一批配置不同的函数”闭包更简洁。amount 0表示非正当存款金额应该拒绝否则可以存入负数导致余额凭空增加。不能。整数是不可变对象闭包只能引用外层变量不能在闭包里直接修改不可变对象。如果要修改必须用nonlocal声明而nonlocal只能指向已经存在的外层变量——整数可以但每次赋值会创建新对象并重新绑定。列表等可变对象可以直接在闭包里修改不需要nonlocal。Python 闭包捕获的是变量的引用不是值本身。这意味着外层变量被修改后闭包下次访问时能得到最新值但也意味着闭包持有对外层对象的引用可能阻止垃圾回收通常不是问题。场景简单只需要一两个函数、需要状态保持、不想引入类的开销、或者需要生成一批配置不同的函数时优先用闭包。需要多种方法、复杂状态、继承体系时用类。闭包可以在很大程度上替代全局变量但不能完全消除所有全局名字如模块级 import、函数定义本身。不过在数据层面用闭包或类封装状态可以做到“零全局可变数据”。本章小结作用域决定变量名的有效范围LEGB 是 Python 的查找顺序。global和nonlocal分别是向模块顶层和外层函数写入变量的声明慎用。闭包是函数记住创建时外层变量的机制通过__closure__和co_freevars可以观测。闭包常用于函数工厂、状态保持、轻量级配置隔离比全局变量更容易测试和维护。下一章预告下一章是第12章《匿名函数与高阶函数》。建议先理解函数是一等公民的概念可以把函数赋值给变量、作为参数传递、作为返回值再学习 lambda 表达式的适用场景和局限。章节导航上一篇第10章《文件读写与路径管理》下一篇第12章《匿名函数与高阶函数》版权声明本文为《Python从入门到精通》系列连载内容面向学习交流使用。版权归作者所有转载须保留出处与章节信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2460870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…