你只知道with,那with该with who呢?

来源:Python 技术「ID: pythonall」

在长期的编程实践中,我们必然已经有过使用下面这段代码的经验:

with open("test.txt", "r", encoding="utf-8") as f: s = f.readlines()

有的人知道这么写的原因;但也有很多人不知道,只是单纯地“别人都这么写,我也应该这么写”。

同时,很多知道原因的人也只是知其然而不知其所以然:with语句可以替我们自动关闭打开的文件对象。但是这是通过什么机制办到的呢?

1. with和异常处理

我们知道,如果不使用with语句的话,正常地读写一个文件应该经过这些过程:打开文件、操作文件、关闭文件。表达为Python代码如下:

f = open("test.txt", "r", encoding="utf-8")s = f.readlines()f.close()

在正常情况下,这样写看起来也没啥问题。

接下来我们就人为制造一点“意外”:把打开文件对象时指定的模式由“r”改为“w”。

f = open("test.txt", "w", encoding="utf-8")s = f.readlines()f.close()

此时,当程序执行到第2行读取文件内容时,就会抛出错误:

Traceback (most recent call last): File "test_with.py", line 2, in <module> s = f.readlines()io.UnsupportedOperation: not readable

然后……一个可怕的情况就发生了。

Python产生未处理的异常从而退出了,导致第2行之后的代码尚未执行,因此**f.close()**也就再也没有机会执行。一个孤魂野鬼般打开的文件对象就这样一个人漂泊在内存的汪洋大海中,没有人知道他是谁、他从哪儿来、他要去哪儿。

就这样,每当抛出一次异常,就会产生这么一个流浪对象。久而久之,内存的汪洋大海也就顺理成章被改造成了流浪者的乐土,其他人想来压根儿没门儿。

追根究底,我们发现导致这个问题的关键在于“打开-操作-关闭”文件这个流水操作中,存在抛出异常的可能。

所以我们想到了使用Python为我们提供的大杀器,来对付这些异常:try-catch。

用异常处理改造一下前面的代码:

try: f = open("test.txt", "a", encoding="utf-8") s = f.readlines()except: print("出现异常")finally: f.close()

这样一来,通过附加的finally语句,无论文件操作是否抛出异常,都能够保证打开的文件被关闭。从而避免了不断占用资源导致资源泄露的问题。

实际上,with语句正是为我们提供了一种try-catch-finally的封装。

编程时,看似只是随随便便的一个with,其实已经暗地里确保了类似于上面代码的异常处理机制。

2. 上下文管理器

with要生效,需要作用于一个上下文管理器——

打住,到底什么是上下文管理器呢?

长话短说,就是实现了__enter____exit__方法的对象。

在进入一个运行时上下文前,会先加载这两个方法以备使用。进入这个运行时上下文时,调用__enter__方法;退出该上下文前,则会调用__exit__方法。

这里的“运行时上下文”,可以简单地理解为一个提供了某些特殊配置的代码作用域。

当我们使用with open("test.txt", "r", encoding="utf-8") as f这句代码时,Python首先对open("test.txt", "r", encoding="utf-8")求值,得到一个上下文管理器。

这里有一点特殊的是,Python中文件对象本身就是一个上下文管理器,因此我们可以使用open函数作为求值的表达式。

随后调用__enter__方法,返回的对象绑定到我们指定的标识符f上。文件对象的__enter__返回文件对象自身,因此这句代码就是将打开的“test.txt”文件对象绑定到了标识符f上。

紧跟着执行with语句块中的内容。

最后调用__exit__,退出with语句块。

根据上面的内容,我们也可以自行构造一个上下文管理器(注意,两个特征方法的参数要与协议一致):

class testContextManager: def __enter__(self): print("进入运行时上下文,调用__enter__方法")
def __exit__(self, exc_type, exc_value, traceback): print("退出运行时上下文,调用__exit__方法")

with testContextManager() as o: pass

输出结果:

进入运行时上下文,调用__enter__方法退出运行时上下文,调用__exit__方法

with语句之所以能够替代繁琐的异常处理语句,正是由于上下文管理器遵循协议实现了__enter____exit__方法,而with语句又确保了发生异常时能够执行完__exit__方法,再退出相关运行时上下文。

在这个方法中,我们就可以完成一些必要的清理工作。

总结

本文我们讲解了with语句的内部逻辑,尝试实现了一个自定义的上下文管理器。相信大家对于with的作用方式有了更深刻的领会。

with语句不仅仅可以用于读写文件,还可以用于锁的自动获取和释放、全局状态的保存和恢复等。更多的实用方式留待大家探索。

参考

[官方文档:8.5. `with` 语句](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#the-with-statement)

[官方文档:3.3.9. with 语句上下文管理器](https://docs.python.org/zh-cn/3/reference/datamodel.html#with-statement-context-managers)

[官方文档:上下文管理器类型](https://docs.python.org/zh-cn/3/library/stdtypes.html#typecontextmanager)

(0)

相关推荐

  • Python 的上下文管理器是怎样设计的?

    设为"星标",重磅干货,第一时间送达 花下猫语:最近,我在看 Python 3.10 版本的更新内容时,发现有一个关于上下文管理器的小更新,然后,突然发现上下文管理器的设计 PEP ...

  • Python中read、readline和readlines的区别?

    公众号新增加了一个栏目,就是每天给大家解答一道Python常见的面试题,反正每天不贪多,一天一题,正好合适,只希望这个面试栏目,给那些正在准备面试的同学,提供一点点帮助! 小猿会从最基础的面试题开始, ...

  • python 中_ _enter_ _ 和 _ _exit_ _ 

    我们前面文章介绍了迭代器和可迭代对象,这次介绍python的上下文管理.在python中实现了__enter__和__exit__方法,即支持上下文管理器协议.上下文管理器就是支持上下文管理器协议的对 ...

  • 我有足够多的钱,却选择住在40平公寓,只为换取幸福

    作者格雷厄姆·希尔是LifeEdited.com和TreeHugger.com的创始人,在年轻时卖掉自己创立的互联网公司,挣了很多钱,买了一个房子和许多东西,甚至雇人做他的私人采购,后来却发现这些东西 ...

  • 姚策养母谈错换人生案伤害:现已家破人亡,起诉医院只为真相

    2021年05月07日 21:59:00 来源:正面FACE 5月8日,姚策养母许敏起诉医院一案将在河南开封开庭,近日,许敏告诉凤凰网正面FACE ,在谈到医院错换对他们家的影响时,她说道:" ...

  • 煮毛豆,不要只会放香料,记得多放一个料,毛豆翠绿不变色

    导语:煮毛豆,不要只会放香料,记得多放一个料,毛豆翠绿不变色 天气越来越热了,又到了吃烧烤的季节,每次去吃烧烤,发现很多人都爱点煮毛豆,有人特别爱吃,一盘吃完又要一盘,我也同样喜欢吃,只要吃烧烤,煮毛 ...

  • 倪海厦:治疗时永远只有三种武器

    一.西医是微视医学(micro) , 中医是巨视医学(macro)   中西医学之不同处是,西医是微视医学(micro) , 中医是巨视医学(macro). 二者有极端不同的见解 , 因此中西医在一起 ...

  • 一个大补心肾的方子,只需2味药材泡水喝

    国医名家 公众号 中医认为"心与夏气相通应",夏季最大的特点就是炎热,此时人体阳气最旺,阳气容易外浮,引起内里虚弱,人就容易出现与心脏相关的疾病.这也是中医常说的,夏天无病自带三分 ...

  • 石家庄二手车中转基地,一六年的大众途安只卖18800?去一探究竟

    石家庄二手车中转基地,一六年的大众途安只卖18800?去一探究竟

  • 锦书难写,只寄相思一点(优美散文)

    作者:暮谣   编辑:夕阳  QQ:1050184607 "自顾影,却下寒塘,正沙净草枯,水平天远,锦书难写,只寄相思一点."在浓墨的夜色里飘来荡去,涤荡着昨日瞬息的美丽:在湿润的 ...

  • 秦军的军功考核标准,真的只看人头数吗?

    作者:原廓 来源:国家人文历史(ID:gjrwls) 提及秦军,很多人第一时间想到的会是军功首级制.有人认为,秦国的士兵只要斩获敌人"甲士"(敌方军官)的一个首级,就可以获得一级爵 ...

  • 一只小碗,从30元到1.41亿的传奇历程...

    本文来源于 网络 来源:腾讯视频---名瓷赏析第二场:举世无双的明代成化青花瓷! 说到成化瓷,是不是以为只有那只曾创下记录.赫赫有名的成化鸡缸杯才有谈资,其实不然,另一件为成化瓷增添神话色彩的青花宫碗 ...

  • 3只金钱豹外逃!发现请拨110…

    据杭州市富阳区人民政府新闻办公室消息,5月7日20时许,群众报警称在富阳区银湖街道受降四联村金苑山庄发现疑似金钱豹.经富阳区相关部门联合调查,确定为杭州野生动物世界三只未成年金钱豹外逃,目前已捕获追回 ...