Python with关键字原理详解

简介: 对于正确的处理涉及到异常的资源管理时,需要使用 `try/finally` 代码结构,这样的结构一多会导致整体代码结构 **很臃肿繁琐,不易读、不美观**,因此在 **Python2.6** 版本推出 `with` 关键字。

引言

对于正确的处理涉及到异常的资源管理时,需要使用 try/finally 代码结构,这样的结构一多会导致整体代码结构 很臃肿繁琐,不易读、不美观,因此在 Python2.6 版本推出 with 关键字。

with as 语句是 Pyhton 提供的一种简化语法,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源。

<br/>

with操作文件

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

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

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

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

<br/>

普通版:

file = "test.txt"

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

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

<br/>

进阶版:

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 代码中,文件就一定会关闭。

<br/>

高级版:

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)

<br/>

上下文管理器

什么是上下文?

上下文在不同的地方表示不同的含义,要感性理解。在编程中 context 上下文其实说白了就是环境。

例如 一个 APP 应用,在切换界面的时候,要保存你是在哪个屏幕跳过来的等等信息,以便你点击返回的时候能正确跳回,如果不存肯定就无法正确跳回了。

再比如线程、协程进行任务切换时,程序怎么能知道切换到另一个任务,是从头开始执行还是从中间呢?其上下文就起到作用,就是任务本身会对其环境进行保存,做到哪里了,做了多少,各种状态都会标识记录,从而形成了上下文环境,因此在切换时根据每个任务的上下文环境,继续执行,从而达到多任务。

<br/>

上下文管理器

任何类实现了 __enter__()__exit__() 方法的对象都可称之为上下文管理器。

上下文管理器对象可以使用 with 关键字。

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 得到的异常信息将作为调用该方法的参数。

<br/>

基于类的上下文管理器

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

"""
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 语句了。其运行结果如下:

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

<br/>

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

__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

<br/>

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

<br/>

基于contextmanager装饰器

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

# 基于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("文件关闭操作")

调用

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

print("with 语句结束")

结果

file_manager() called
with 代码块
文件关闭操作
with 语句结束

<br/>

总结

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

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

<br/>

源代码

源代码已上传到 Gitee PythonKnowledge: Python知识宝库,欢迎大家来访。

✍ 码字不易,还望各位大侠多多支持❤️。

<br/>

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。
相关文章
|
3月前
|
安全 API 数据安全/隐私保护
深入浅出python代码混淆:原理与实践
代码混淆就像是给你的代码穿上了一件隐形衣。它可以让你的代码变得难以理解,但并不能完全保证代码的安全。在实际应用中,我们应该将代码混淆作为整个安全策略中的一环,而不是唯一的防线。
|
3月前
|
机器学习/深度学习 存储 算法
回声状态网络(Echo State Networks,ESN)详细原理讲解及Python代码实现
本文详细介绍了回声状态网络(Echo State Networks, ESN)的基本概念、优点、缺点、储层计算范式,并提供了ESN的Python代码实现,包括不考虑和考虑超参数的两种ESN实现方式,以及使用ESN进行时间序列预测的示例。
145 4
回声状态网络(Echo State Networks,ESN)详细原理讲解及Python代码实现
|
2月前
|
测试技术 开发者 Python
深入浅出:Python中的装饰器使用与原理解析
【9月更文挑战第20天】本文深入探讨Python中一个强大而神秘的功能——装饰器。通过浅显易懂的语言和生动的比喻,我们将一步步揭开装饰器的面纱,理解其背后的原理,并通过实际代码示例掌握如何运用装饰器来增强我们的函数功能。无论你是初学者还是有一定基础的开发者,这篇文章都将带给你新的启发和思考。
42 7
|
2月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
23 1
|
2月前
|
API 开发者 Python
Python中的魔法方法:从原理到实践
【9月更文挑战第24天】本文将深入探讨Python的魔法方法,这些特殊的方法允许对象定制其行为。文章首先揭示魔法方法的本质和重要性,然后通过代码示例展示如何利用它们来增强类的功能性。最后,我们将讨论在实际应用中应注意的事项,以确保正确和高效地使用这些方法。
|
2月前
|
中间件 API 开发者
深入理解Python Web框架:中间件的工作原理与应用策略
在Python Web开发中,中间件位于请求处理的关键位置,提供强大的扩展能力。本文通过问答形式,探讨中间件的工作原理、应用场景及实践策略,并以Flask和Django为例展示具体实现。中间件可以在请求到达视图前或响应返回后执行代码,实现日志记录、权限验证等功能。Flask通过装饰器模拟中间件行为,而Django则提供官方中间件系统,允许在不同阶段扩展功能。合理制定中间件策略能显著提升应用的灵活性和可扩展性。
38 4
|
2月前
|
缓存 Python
探索Python中的装饰器:原理与应用
本文深入探讨了Python中装饰器的概念,从基本定义到实际应用进行了系统性的阐述。通过实例展示了如何利用装饰器来增强函数功能,同时详细解释了其背后的运行机制和实现原理。此外,文章还讨论了装饰器在软件开发中的实际应用场景,为读者提供了实用的编程技巧和最佳实践。
30 3
|
29天前
|
数据采集 调度 Python
Python编程异步爬虫——协程的基本原理(一)
Python编程异步爬虫——协程的基本原理(一)
|
29天前
|
数据采集 Python
Python编程异步爬虫——协程的基本原理(二)
Python编程异步爬虫——协程的基本原理(二)
|
1月前
|
Java C语言 Python
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
解析Python中的全局解释器锁(GIL):影响、工作原理及解决方案
32 0