generator

张开发
2026/4/3 23:59:04 15 分钟阅读
generator
## 关于 Python 中的上下文管理器在 Python 里写代码有时候会遇到一些需要“善后”的场景。比如打开一个文件用完之后得记得关掉或者连接数据库操作完也得断开。这些操作如果忘了可能会出问题比如文件被占用、数据库连接数不够用之类的。上下文管理器就是用来处理这类问题的。它提供了一种结构化的方式来确保某些操作在执行前后能被正确处理。最典型的用法就是with语句。举个例子读写文件的时候一般会这么写withopen(data.txt,r)asf:contentf.read()这里open()返回的就是一个上下文管理器。在进入with块的时候文件被打开并赋值给f离开with块的时候无论中间有没有出错文件都会被自动关闭。这比手动写try...finally要简洁不少。它是怎么工作的上下文管理器背后其实有两个特殊方法__enter__和__exit__。__enter__在进入with块时调用返回值会赋给as后面的变量__exit__在离开with块时调用负责处理清理工作比如关文件、关连接、释放锁等等。自己实现一个上下文管理器也不难。比如模拟一个简单的“计时器”classTimer:def__enter__(self):importtime self.starttime.time()returnselfdef__exit__(self,exc_type,exc_val,exc_tb):importtime self.endtime.time()print(f耗时:{self.end-self.start:.2f}秒)withTimer()ast:# 模拟一些耗时操作importtime time.sleep(1)这样代码块执行的时间就会被自动打印出来。更轻便的写法如果觉得写一个类有点重也可以用contextlib模块里的contextmanager装饰器通过生成器来快速实现。上面的计时器可以改写成这样fromcontextlibimportcontextmanagerimporttimecontextmanagerdeftimer():starttime.time()yieldendtime.time()print(f耗时:{end-start:.2f}秒)withtimer():time.sleep(1)这种写法更函数式适合一些简单的场景。yield之前的部分相当于__enter__之后的部分相当于__exit__。实际用在哪里上下文管理器不只是用来开关文件。比如在多线程里管理锁可以确保锁一定会被释放importthreading lockthreading.Lock()withlock:# 临界区代码pass再比如临时修改某个配置执行完再改回来importsysfromcontextlibimportredirect_stdoutwithopen(output.log,w)asf,redirect_stdout(f):print(这行内容会被写入文件而不是打印到屏幕)这里还展示了同时使用多个上下文管理器用逗号隔开就行。一点细节__exit__方法会接收到三个参数分别代表异常类型、异常值和 traceback。如果代码块正常执行它们都是None。如果发生异常__exit__可以选择处理异常返回True或者让异常继续向上抛返回False。这让上下文管理器不仅能做清理还能做错误处理。比如可以写一个忽略特定错误的上下文管理器classIgnoreError:def__enter__(self):passdef__exit__(self,exc_type,exc_val,exc_tb):ifexc_typeValueError:print(忽略 ValueError)returnTruereturnFalsewithIgnoreError():raiseValueError(这个错误会被忽略)最后# ## 生成器一个被低估的Python核心概念很多人在学习Python的时候都会遇到“生成器”这个概念。教科书上通常会说生成器是一种特殊的迭代器使用yield关键字定义能够节省内存。这些说法都没错但总觉得少了点什么——就像只告诉了你汽车有四个轮子能跑却没告诉你它真正的驾驶体验是什么。生成器最核心的特质其实是一种“惰性思维”。这种思维方式和我们的日常习惯不太一样。我们通常习惯一次性把所有事情准备好比如要请朋友吃饭会提前买好所有食材洗好切好然后才开始炒菜。但生成器不是这样它是“边做边吃”的模式——客人来了才开始洗菜一边炒一边上桌。这种模式在编程中特别有用。举个例子处理一个几十GB的日志文件时如果一次性读入内存电脑可能就直接卡死了。用生成器的话可以一行一行地读处理完一行就释放一行的内存整个过程流畅自然。这不仅仅是技术上的优化更是一种思维方式的转变。yield这个关键字很有意思它让函数有了“记忆”。普通的函数调用就像是一次性的对话问完答完就结束了。但带有yield的函数不同它会在中途暂停记住自己停在哪里下次还能从暂停的地方继续。这种特性让生成器特别适合处理那些需要“中间状态”的任务。在实际项目中生成器经常被用在数据管道中。想象一下工厂的流水线原材料从一端进去经过多个加工环节最终变成产品。生成器表达式和生成器函数可以很自然地构建这样的管道每个环节只处理流经自己的数据然后传递给下一个环节。代码不仅更清晰而且因为惰性求值的特性整个处理过程可以更高效。有些开发者可能会觉得生成器表达式看起来太简洁担心可读性。确实一行代码里塞太多逻辑不是好习惯。但适当地使用生成器表达式特别是在数据转换和过滤的场景下代码会变得非常优雅。比如从一堆数据里筛选出符合条件的项然后做某种转换用生成器表达式写出来几乎就像在描述业务逻辑本身。还有一个容易被忽略的点是生成器只能遍历一次。这个特性初看像是限制但换个角度想它其实在提醒我们有些数据流本来就是一次性的。就像实时视频流过去了就过去了不会倒回来重新播放。理解这一点就能更好地设计那些处理流式数据的系统。在异步编程中生成器的思想也得到了延伸。虽然asyncio用的是async/await语法但那种“暂停-恢复”的执行模式和生成器的核心理念是一脉相承的。理解了生成器再去学异步编程会有种豁然开朗的感觉。说到底生成器不仅仅是一个语法特性它代表了一种处理数据的哲学不是所有东西都需要立即拥有按需获取往往更优雅。在数据量越来越大的今天这种思维显得尤为珍贵。下次写代码时如果遇到需要处理大量数据或者复杂数据流的情况不妨想想这里用生成器会不会更合适上下文管理器是 Python 里一个挺实用的特性。它把“准备”和“清理”的逻辑封装在一起让代码更清晰也更容易维护。平时写代码的时候如果遇到成对出现的操作比如打开关闭、加锁解锁、开始结束都可以考虑用上下文管理器来组织。时间长了代码会显得更有条理也不容易漏掉那些关键的清理步骤。

更多文章