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的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。
相关文章
|
1月前
|
机器学习/深度学习 Python
堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能
本文深入探讨了堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能。文章详细介绍了堆叠的实现步骤,包括数据准备、基础模型训练、新训练集构建及元学习器训练,并讨论了其优缺点。
52 3
|
1月前
|
机器学习/深度学习 算法 数据挖掘
线性回归模型的原理、实现及应用,特别是在 Python 中的实践
本文深入探讨了线性回归模型的原理、实现及应用,特别是在 Python 中的实践。线性回归假设因变量与自变量间存在线性关系,通过建立线性方程预测未知数据。文章介绍了模型的基本原理、实现步骤、Python 常用库(如 Scikit-learn 和 Statsmodels)、参数解释、优缺点及扩展应用,强调了其在数据分析中的重要性和局限性。
62 3
|
8天前
|
C语言 Python
[oeasy]python054_python有哪些关键字_keyword_list_列表_reserved_words
本文介绍了Python的关键字列表及其使用规则。通过回顾`hello world`示例,解释了Python中的标识符命名规则,并探讨了关键字如`if`、`for`、`in`等不能作为变量名的原因。最后,通过`import keyword`和`print(keyword.kwlist)`展示了Python的所有关键字,并总结了关键字不能用作标识符的规则。
25 9
|
12天前
|
缓存 数据安全/隐私保护 Python
python装饰器底层原理
Python装饰器是一个强大的工具,可以在不修改原始函数代码的情况下,动态地增加功能。理解装饰器的底层原理,包括函数是对象、闭包和高阶函数,可以帮助我们更好地使用和编写装饰器。无论是用于日志记录、权限验证还是缓存,装饰器都可以显著提高代码的可维护性和复用性。
26 5
|
25天前
|
缓存 开发者 Python
深入探索Python中的装饰器:原理、应用与最佳实践####
本文作为技术性深度解析文章,旨在揭开Python装饰器背后的神秘面纱,通过剖析其工作原理、多样化的应用场景及实践中的最佳策略,为中高级Python开发者提供一份详尽的指南。不同于常规摘要的概括性介绍,本文摘要将直接以一段精炼的代码示例开篇,随后简要阐述文章的核心价值与读者预期收获,引领读者快速进入装饰器的世界。 ```python # 示例:一个简单的日志记录装饰器 def log_decorator(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with args: {a
37 2
|
1月前
|
机器学习/深度学习 人工智能 算法
强化学习在游戏AI中的应用,从基本原理、优势、应用场景到具体实现方法,以及Python在其中的作用
本文探讨了强化学习在游戏AI中的应用,从基本原理、优势、应用场景到具体实现方法,以及Python在其中的作用,通过案例分析展示了其潜力,并讨论了面临的挑战及未来发展趋势。强化学习正为游戏AI带来新的可能性。
100 4
|
1月前
|
测试技术 开发者 Python
对于Python中的异常要如何处理,raise关键字你真的了解吗?一篇文章带你从头了解
`raise`关键字在Python中用于显式引发异常,允许开发者在检测到错误条件时中断程序流程,并通过异常处理机制(如try-except块)接管控制。`raise`后可跟异常类型、异常对象及错误信息,适用于验证输入、处理错误、自定义异常、重新引发异常及测试等场景。例如,`raise ValueError(&quot;Invalid input&quot;)`用于验证输入数据,若不符合预期则引发异常,确保数据准确并提供清晰错误信息。此外,通过自定义异常类,可以针对特定错误情况提供更具体的信息,增强代码的健壮性和可维护性。
|
1月前
|
搜索推荐 Python
快速排序的 Python 实践:从原理到优化,打造你的排序利器!
本文介绍了 Python 中的快速排序算法,从基本原理、实现代码到优化方法进行了详细探讨。快速排序采用分治策略,通过选择基准元素将数组分为两部分,递归排序。文章还对比了快速排序与冒泡排序的性能,展示了优化前后快速排序的差异。通过这些分析,帮助读者理解快速排序的优势及优化的重要性,从而在实际应用中选择合适的排序算法和优化策略,提升程序性能。
44 1
WK
|
2月前
|
Python
Python保留关键字
Python 的保留关键字是语言中具有特殊意义的单词,不能用作变量名、函数名或类名等标识符。Python 3.x 版本中的保留关键字包括 `False`、`await`、`else`、`import` 等共 35 个。使用这些关键字作为标识符会导致语法错误。随着 Python 版本的更新,保留关键字可能会有所变化。
WK
47 6
|
2月前
|
Java 程序员 C++
【Python】链式、嵌套调用、递归、函数栈帧、参数默认值和关键字参数
【Python】链式、嵌套调用、递归、函数栈帧、参数默认值和关键字参数
36 0
【Python】链式、嵌套调用、递归、函数栈帧、参数默认值和关键字参数