geekdoc-python-zh/docs/pythonlibrary/pythons-with-statement-and-...

8.0 KiB
Raw Permalink Blame History

Python 的语句和上下文管理器

原文:https://www.blog.pythonlibrary.org/2021/04/07/pythons-with-statement-and-context-managers/

几年前Python 2.5 中出现了一个特殊的新关键字,即带有和语句的 。这个新关键字允许开发人员创建上下文管理器。但是等等!什么是上下文管理器?它们是方便的构造,允许你自动设置和拆除某些东西。例如,你可能想打开一个文件,在里面写一些东西,然后关闭它。这可能是上下文管理器的经典例子。事实上,当您使用带有 语句的 打开一个文件时Python 会自动为您创建一个:

with open(path, 'w') as f_obj:
    f_obj.write(some_data)

回到 Python 2.4,您必须用老式的方法来做:

f_obj = open(path, 'w')
f_obj.write(some_data)
f_obj.close()

这在幕后的工作方式是通过使用 Python 的一些神奇方法: enterexit 。让我们尝试创建您自己的上下文管理器来演示这一切是如何工作的!

创建上下文管理器类

这里不用重写 Python 的 open 方法,而是创建一个上下文管理器,它可以创建一个 SQLite 数据库连接,并在连接完成后关闭它。这里有一个简单的例子:

import sqlite3

class DataConn:
    """"""

    def __init__(self, db_name):
        """Constructor"""
        self.db_name = db_name

    def __enter__(self):
        """
        Open the database connection
        """
        self.conn = sqlite3.connect(self.db_name)
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Close the connection
        """
        self.conn.close()
        if exc_val:
            raise

if __name__ == '__main__':
    db = '/home/mdriscoll/test.db'
    with DataConn(db) as conn:
        cursor = conn.cursor()

在上面的代码中,您创建了一个获取 SQLite 数据库文件路径的类。 enter 方法自动执行,创建并返回数据库连接对象。现在您已经有了,您可以创建一个游标并写入数据库或查询它。当您用 语句退出 时,它会导致 exit 方法执行并关闭连接。

让我们尝试使用另一种方法创建一个上下文管理器。

使用 contextlib 创建上下文管理器

Python 2.5 不仅用 语句添加了 ,还添加了 contextlib 模块。这允许您使用 contextlib 的 contextmanager 函数作为装饰器来创建上下文管理器。

让我们试着创建一个打开和关闭文件的上下文管理器:

from contextlib import contextmanager

@contextmanager
def file_open(path):
    try:
        f_obj = open(path, 'w')
        yield f_obj
    except OSError:
        print("We had an error!")
    finally:
        print('Closing file')
        f_obj.close()

if __name__ == '__main__':
    with file_open('/home/mdriscoll/test.txt') as fobj:
        fobj.write('Testing context managers')

这里你从的 contextlib 中导入的 contextmanager ,并用它来修饰你的 file_open() 函数。这允许你使用 Python 的 语句调用 file_open ()。在您的函数中,您打开文件,然后将它输出,以便调用函数可以使用它。

一旦带有语句的结束,控制返回到 file_open() ,并继续执行 yield 语句之后的代码。这导致执行 finally 语句,关闭文件。如果在处理文件时碰巧有一个 OSError ,它会被捕获,并且 finally 语句仍然会关闭文件处理程序。

contextlib.closing()

contextlib 模块附带了一些其他方便的实用程序。第一个是 closing它将在代码块完成时关闭这个东西。Python 文档提供了一个类似于以下示例的示例:

from contextlib import contextmanager

@contextmanager
def closing(db):
    try:
        yield db.conn()
    finally:
        db.close()

基本上,您所做的是创建一个封装在 contextmanager 中的关闭函数。这相当于结束类所做的工作。不同之处在于,您可以在 with 语句中使用结束类本身,而不是装饰器。

这将会是这样的:

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('http://www.google.com')) as webpage:
    for line in webpage:
        # process the line
        pass

在这个例子中,你打开一个 URL但是用你的结束类把它包装起来。这将导致一旦你从语句的代码块中掉出,网页的句柄就会被关闭。

**### context lib . suppress(*异常)

另一个方便的小工具是 Python 3.4 中添加的 suppress 类。这个上下文管理器工具背后的思想是它可以抑制任意数量的异常。一个常见的例子是当您想要忽略 FileNotFoundError 异常时。如果您要编写以下上下文管理器,它将不起作用:

>>> with open('fauxfile.txt') as fobj:
        for line in fobj:
            print(line)

Traceback (most recent call last):
  Python Shell, prompt 4, line 1
builtins.FileNotFoundError: [Errno 2] No such file or directory: 'fauxfile.txt'

这个上下文管理器不处理这个异常。如果您想忽略此错误,则可以执行以下操作:

from contextlib import suppress

with suppress(FileNotFoundError):
    with open('fauxfile.txt') as fobj:
        for line in fobj:
            print(line)

在这里,您导入 suppress 并向其传递您想要忽略的异常,在本例中是 FileNotFoundError 异常。如果运行这段代码,不会发生任何事情,因为文件不存在,但也不会引发错误。应该注意的是,这个上下文管理器是重入。这将在本文后面解释。

contextlib.redirect_stdout / redirect_stderr

contextlib 库有两个用于重定向 stdout 和 stderr 的工具,它们分别是在 Python 3.4 和 3.5 中添加的。在添加这些工具之前,如果您想重定向 stdout您应该这样做:

path = '/path/to/text.txt'

with open(path, 'w') as fobj:
    sys.stdout = fobj
    help(sum)

使用 contextlib 模块,您现在可以执行以下操作:

from contextlib import redirect_stdout

path = '/path/to/text.txt'
with open(path, 'w') as fobj:
    with redirect_stdout(fobj):
        help(redirect_stdout)

在这两个例子中,您将 stdout 重定向到一个文件。当您调用 Python 的 help() 时,它不是打印到 stdout而是直接保存到文件中。您还可以从 Tkinter 或 wxPython 等用户界面工具包中将 stdout 重定向到某种缓冲区或文本控件类型的小部件。

退出堆栈

ExitStack 是一个上下文管理器,它允许你很容易地以编程方式结合其他上下文管理器和清理功能。起初这听起来有点令人困惑,所以让我们来看看 Python 文档中的一个例子,以帮助您更好地理解这个想法:

>>> from contextlib import ExitStack
>>> with ExitStack() as stack:
        file_objects = [stack.enter_context(open(filename))
            for filename in filenames]
                    ]

这段代码基本上在 list comprehension 中创建了一系列上下文管理器。 ExitStack 维护了一个注册回调的堆栈,当实例关闭时,它会以相反的顺序调用这些回调,这发生在用语句退出的底部时。

在 Python 文档中有许多关于 contextlib 的简洁示例,您可以从中了解如下主题:

  • enter 方法捕获异常

  • 支持可变数量的上下文管理器

  • 取代任何 try-finally 的使用

  • 还有更多!

你应该去看看,这样你就能很好地感受到这个类有多强大。

包扎

上下文管理器非常有趣,并且总是很方便。例如,我在自动化测试中一直使用它们来打开和关闭对话框。现在,您应该能够使用 Python 的一些内置工具来创建自己的上下文管理器。请务必花时间阅读 contextlib 上的 Python 文档,因为还有很多本章没有涉及的其他信息。开心快乐编码!**