前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python with关键字原理详解

Python with关键字原理详解

作者头像
ZackSock
发布2021-04-13 15:15:16
4460
发布2021-04-13 15:15:16
举报
文章被收录于专栏:ZackSockZackSock

引言

对于正确的处理涉及到异常的资源管理时,需要使用 try/finally 代码结构,这样的结构一多会导致整体代码结构 很臃肿繁琐,不易读、不美观,因此在 Python2.6 版本推出 with 关键字。 with as 语句是 Pyhton 提供的一种简化语法,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源。

with操作文件

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(释放)该资源

比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,如果不关闭会出现什么情况呢?极端情况下会出现 Too many open files 的错误,因为系统允许你打开的最大文件数量是有限的。

同样,对于数据库,如果连接数过多而没有及时关闭的话,就可能会出现 Can not connect to MySQL server Too many connections,因为数据库连接是一种非常昂贵的资源,不可能无限制的被创建。

在代码中经常会看见 with open(file) as f 对文件进行操作,其中 with 关键字到底有什么用处呢?让我们一起了解一下其底层原理。来看看如何正确关闭一个文件。

普通版:
代码语言:javascript
复制
file = "test.txt"

def fun1():
    """
    普通版
    """
    f = open(file, "w")
    f.write("hello python")
    f.close

这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用者释放。那么该如何改进代码呢?

进阶版:
代码语言:javascript
复制
file = "test.txt"

def fun2():
    """
    异常处理
    """
    try:
        f = open(file, "w")
        f.write("hello python")
    except Exception as e:
        print(e)
    finally:
        f.close()

改良版本的程序是对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,该语句表示如果在 try代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块。而无论如何,finally 块的代码最终都会被执行。因此,只要把 close 放在 finally 代码中,文件就一定会关闭。

高级版:
代码语言:javascript
复制
file = "test.txt"

def fun3():
    """
    with 关键字
    """
    with open(file, "w") as f:
        f.write("hello python")

一种更加简洁、优雅的方式就是用 with 关键字。open 方法的返回值赋值给变量 f,当离开 with 代码块的时候,系统会自动调用 f.close() 方法, with 的作用和使用 try/finally 语句是一样的。那么它的实现原理是什么?在讲 with 的原理前要涉及到另外一个概念,就是 上下文管理器(Context Manager)

上下文管理器

什么是上下文?

上下文在不同的地方表示不同的含义,要感性理解。在编程中 context 上下文其实说白了就是环境。 例如 一个 APP 应用,在切换界面的时候,要保存你是在哪个屏幕跳过来的等等信息,以便你点击返回的时候能正确跳回,如果不存肯定就无法正确跳回了。 再比如线程、协程进行任务切换时,程序怎么能知道切换到另一个任务,是从头开始执行还是从中间呢?其上下文就起到作用,就是任务本身会对其环境进行保存,做到哪里了,做了多少,各种状态都会标识记录,从而形成了上下文环境,因此在切换时根据每个任务的上下文环境,继续执行,从而达到多任务。

上下文管理器

任何类实现了 __enter__()__exit__() 方法的对象都可称之为上下文管理器。 上下文管理器对象可以使用 with 关键字。

代码语言:javascript
复制
file = "test.txt"

def fun3():
    """
    with 关键字
    """
    with open(file, "w") as f:
        # with 代码块
        f.write("hello python")
        
    print("with 语句结束")
  • __enter__(self):进入上下文管理器自动调用的方法,该方法会在 with as 代码块 执行之前执行。如果 with 语句有 as子句,那么该方法的返回值会被赋值给 as 子句后的变量;该方法可以返回多个值,因此在 as 子句后面也可以指定多个变量(多个变量必须由()括起来组成元组)。
  • __exit__(self, exc_type, exc_value, exc_traceback):退出上下文管理器自动调用的方法。该方法会在 with as 代码块 执行之后执行。如果 with as 代码块成功执行结束,程序自动调用该方法,调用该方法的三个参数都为 None,如果 with as 代码块 因为 异常而中止,程序也自动调用该方法,使用 sys.exc_info 得到的异常信息将作为调用该方法的参数。
基于类的上下文管理器

我们可以模拟实现一个自己的文件类,让该类实现 __enter__()__exit__()方法。

代码语言:javascript
复制
"""
with关键字的实现原理
上下文管理器
"""

# 基于类实现上下文管理器
class File(object):

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        """
        进入with as 语句的时候被with调用
        返回值作为 as 后面的变量
        """
        print("__enter__ called")
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_value, exc_traceback):
        """
        离开with语句的时候被with调用
        """
        print("__exit__ called")
        print("exc_type: ", exc_type)
        print("exc_value: ", exc_value)
        print("exc_traceback: ", exc_traceback)
        self.file.close()
        print("文件关闭操作")


def main():

    with File("test.txt", "w") as f:
        print("with 代码块")
        f.write("hello python1")
        f.write("hello python2")
        # a = 1 / 0
        f.write("hello python3")

    print("with 语句结束")


if __name__ == '__main__':
    main()

__enter__() 方法返回资源对象,这里就是你将要打开的那个文件对象,__exit__()方法处理一些清除工作。

因为 File 类实现了上下文管理器,现在就可以使用 with 语句了。其运行结果如下:

代码语言:javascript
复制
__enter__ called
with 代码块
__exit__ called
exc_type:  None
exc_value:  None
exc_traceback:  None
文件关闭操作
with 语句结束

除0异常 的代码注释去了看看结果

代码语言:javascript
复制
__enter__ called
with 代码块
__exit__ called
exc_type:  <class 'ZeroDivisionError'>
exc_value:  division by zero
exc_traceback:  <traceback object at 0x0000021F0780BCC8>
文件关闭操作
Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\pycode\withdemo.py", line 113, in <module>
    main()
  File "C:\Users\Administrator\Desktop\pycode\withdemo.py", line 106, in main
    a = 1 / 0
ZeroDivisionError: division by zero

这样,你就无需显示地调用 close 方法了,由系统自动去调用,哪怕中间遇到异常 close 方法也会被调用。

基于contextmanager装饰器

Python 在 contextlib 模块中还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 __enter__ 方法中执行,yield 之后的语句在 __exit__ 方法中执行。紧跟在 yield 后面的值是函数的返回值。

代码语言:javascript
复制
# 基于contextmanager装饰器
from contextlib import contextmanager

@contextmanager
def file_manager(name, mode):
    print("file_manager() called")
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()
        print("文件关闭操作")

调用

代码语言:javascript
复制
with file_manager('test.txt', 'w') as f:
    print("with 代码块")
    f.write('hello world')
    # a = 1 / 0

print("with 语句结束")

结果

代码语言:javascript
复制
file_manager() called
with 代码块
文件关闭操作
with 语句结束

总结

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下文管理器的实现方式。基于类和基于 contextmanager 的上下文管理器,这两者在功能上是一致的。只不过,基于类的上下文管理器 更加灵活,适用于大型的系统开发,而基于 contextmanager 的上下文管理器 更加方便、简洁,适用于中小型程序

无论使用哪一种,不要忘记在方法 __exit__() 或者是 finally 块中释放资源,这一点尤其重要。

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 新建文件夹X 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • with操作文件
    • 普通版:
      • 进阶版:
        • 高级版:
        • 上下文管理器
          • 什么是上下文?
            • 上下文管理器
              • 基于类的上下文管理器
              • 基于contextmanager装饰器
            • 总结
            • 公众号
            相关产品与服务
            云数据库 MySQL
            腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档