理解 Python 的上下文管理器

本贴最后更新于 2169 天前,其中的信息可能已经渤澥桑田

原文载于 https://old-panda.com/2019/04/17/python-context-manager/

任何 Python 教程,必然会讲解如何打开一个文件。而任何提到打开文件的地方,都必然会推荐用 with 来操作文件的读写。比如说这里有一篇非常优秀的教程,文中提到

在 Python 中,文件读写是通过 open() 函数打开的文件对象完成的。使用 with 语句操作文件 IO 是个好习惯。

并且给出了详细的代码示例。但为什么 with 关键字能在结束这个 block 的时候自动调用 close() 呢?让我们去一探究竟。

首先来看一下 with 是怎么来的。 with 被提出是在 PEP 343 ,其中有段对于 with 操作的详细说明。简单来说,执行下面代码的前提是,要求 EXPR 的类实现了 __enter__ 和 __exit__ 方法。

with EXPR as VAR: BLOCK

进入 with block 之后,第一件事就是把 __enter__ 的返回值赋给 VAR ,然后执行 BLOCK 的内容,无论能否顺利执行,最终都会执行 __exit__ 方法来“收拾残局”,在我们的情况下,即关闭文件。

了解了 with 的原理,那么 open 函数又是如何提供 __enter__ 和 __exit__ 方法的呢?让我们去源码里找找蛛丝马迹。当我们写下 open() 某个文件时, Python 实际上调用的是这里,这个函数实质上返回的是类 [TextIOWrapper](https://github.com/python/cpython/blob/3.7/Lib/_pyio.py#L1908) 的实例。我们可以通过 Python shell 来证实。

>>> open("test.txt") <_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>

这个类继承自 [TextIOBase](https://github.com/python/cpython/blob/3.7/Lib/_pyio.py#L1756) ,而 TextIOBase 又继承自 [IOBase](https://github.com/python/cpython/blob/3.7/Lib/_pyio.py#L281) ,这个类就是所有文件 IO 类的基类,所有文件读写的类都继承了它。通过阅读这个基类,我们发现其中有这么一段代码

... ### Context manager ### def __enter__(self): # That's a forward reference """Context management protocol. Returns self (an instance of IOBase).""" self._checkClosed() return self def __exit__(self, *args): """Context management protocol. Calls close()""" self.close() ...

于是我们找到了原因,当通过 with 来打开一个文件时,我们得到的是一个 IOBase 子类的实例,这个实例提供各种读写文件的方法,当退出 with 代码块时,调用文件关闭方法。这样我们就不用编写大段的 try...except...finally 来确保文件安全关闭了。

PEP 343 说了,只要一个类能提供 __enter__ 和 __exit__ 方法,我们就能用 with 来保证某个操作在代码执行完毕后能继续执行,作为收尾。我们试着写一个简单的类,来证明我们的理解是对的。要求在 with 结束后,打印出一句我最喜欢的诗句。

>>> class Foo(object): ... def __init__(self, bar): ... self.bar = bar ... def __enter__(self): ... return self ... def __exit__(self, *args): ... print("苟利国家生死以,岂因祸福避趋之") ... >>> with Foo("naive") as foo: ... print(foo.bar) ... naive 苟利国家生死以,岂因祸福避趋之

非常简单的一个类,就保证了每次代码执行之后,都能在屏幕上打印出这样一句话。但这样仍然稍显麻烦,毕竟我想的是能少些两句就少些两句,每次专门写这样一个类,还必须实现那两个方法,想想就挺啰嗦,有没有更好的办法?办法就在这句“上下文管理器”的注释里 ### Context manager ### ,稍微搜索一下,我们就能找到这样一篇文档,参照代码示例,我们不难写出一段简单的代码来实现同样的功能。

>>> from contextlib import contextmanager >>> >>> @contextmanager ... def foo(bar): ... yield bar ... print("苟利国家生死以,岂因祸福避趋之") ... >>> with foo("naive") as f: ... print(f) ... naive 苟利国家生死以,岂因祸福避趋之

这里 yield 抛出的是我们真正感兴趣的内容,后续则是 with 块结束后我们必须进行的操作。

  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    556 引用 • 675 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 298 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 346 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 5 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    21 引用 • 31 回帖
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 16 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    729 引用 • 1278 回帖
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖 • 4 关注
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    335 引用 • 324 回帖 • 1 关注
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 34 关注
  • AWS
    11 引用 • 28 回帖 • 11 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 167 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 445 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖 • 2 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 495 关注
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • 工具

    子曰:“工欲善其事,必先利其器。”

    297 引用 • 755 回帖
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 630 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 4 关注
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 390 关注
  • Anytype
    3 引用 • 31 回帖 • 15 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 612 关注
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖
  • sts
    2 引用 • 2 回帖 • 223 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖