对象支持上下文管理协议 (with 语句)

本文介绍了如何使用上下文管理器协议with语句创建网络连接,包括类实现__enter__和__exit__方法,以及使用@contextmanager装饰器简化代码。同时讨论了如何处理嵌套连接和资源管理的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方式一:

为了让一个对象兼容 with 语句,你需要实现 __enter__ ()__exit__ () 方法。例如,考虑如下的一个类,它能为我们创建一个网络连接:

# -*- coding: utf-8 -*-
"""
上下文管理器协议 with 语句

————enter————
————exit————
"""
from functools import partial
from socket import socket, AF_INET, SOCK_STREAM


class LazyConnection:

    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):   # with open(...) as fp: 调用这个方法,返回值就是fp
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):  # 不管正常还是异常,都调用这个方法,关闭套接字
        self.sock.close()
        self.sock = None


conn = LazyConnection(('www.python.org', 80))

with conn as s:
    s.send(b'GET /index.html HTTP/1.0\r\n')
    s.send(b'Host: www.python.org\r\n')
    s.send(b'\r\n')
    resp = b''.join(iter(partial(s.recv, 8192), b''))
    print(resp)



编写上下文管理器的主要原理是你的代码会放到 with 语句块中执行。 当出现 with语句的时候,对象的 __enter__ () 方法被触发,它返回的值 (如果有的话) 会被赋值as 声明变量。然后, with 语句块里面的代码开始执行。最后, __exit__ () 方法被触发进行清理工作(释放资源)。

不管 with 代码块中发生什么,上面的控制流都会执行完,就算代码块发生了异常也是一样的。事实上, __exit__ () 方法的三个参数包含了异常类型异常值追溯信息 (如果有的话)。 __exit__ () 方法能自己决定怎样利用这个异常信息,或者忽略它并返回一个 None 值。如果 __exit__ () 返回 True ,那么异常被清空,就好像什么都没发生一样, with 语句后面的程序继续在正常执行。

还有一个细节问题就是 LazyConnection是否允许多个 with 语句嵌套使用连接。很显然,上面的定义中一次只能允许一个 socket 连接,如果正在使用一个 socket的时候又重复使用 with 语句,就会产生一个异常了。不过你可以像下面这样修改下上面的实现来解决这个问题:


class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.connections = []

    def __enter__(self):
        sock = socket(self.family, self.type)
        sock.connect(self.address)
        self.connections.append(sock)
        return sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.connections.pop().close()


conn = LazyConnection(('www.python.org', 80))
with conn as s1:
    pass
    with conn as s2:
        pass

在第二个版本中, LazyConnection 类可以被看做是某个连接工厂。在内部,一个列表被用来构造一个栈。每次 __enter__ () 方法执行的时候,它复制创建一个新的连接并将其加入里面。 __exit__ () 方法简单从栈中弹出最后一个连接并关闭它。这里稍微有点难理解,不过它能允许嵌套使用 with 语句创建多个连接,就如上面演示的那样。

在需要管理一些资源比如文件网络连接的编程环境中,使用上下文管理器是很普遍的。这些资源的一个主要特征是它们必须被手动的关闭或释放来确保程序的正确运行。例如,如果你请求了一个锁,那么你必须确保之后释放了它,否则就可能产生死锁。通过实现__enter__ ()__exit__ () 方法并使用 with 语句可以很容易的避免这些问题,因为 __exit__ () 方法可以让你无需担心这些了。

方式二:

使用@contextmanagerr 装饰器能减少创建上下文管理器样板代码量,因为不用编写一个完整的类,定义 __enter____exit__ 方法,而只需实现有一个 yield 语句的生成器,生成想让__enter__ 方法返回的值

在使用 @contextmanager 装饰的生成器中yield 语句的作用是把函数的定义体分成两部分yield 语句前面的所有代码with 块开始时(即解释器调用 __enter__ 方法时)执行,yield 语句后面的代码with 块结束时(即调用 __exit__ 方法时)执行。

# -*- coding: utf-8 -*-
"""
更改输出流
"""
import contextlib


@contextlib.contextmanager  # 应用 contextmanager 装饰器。
def looking_glass():
    import sys
    original_write = sys.stdout.write  # 贮存原来的 sys.stdout.write 方法

    def reverse_write(text):  # 定义自定义的 reverse_write 函数;在闭包中可以访问 original_write
        original_write(text[::-1])

    sys.stdout.write = reverse_write  # 把 sys.stdout.write 替换成 reverse_write

    # 产出一个值,这个值会绑定到 with 语句中 as 子句的目标变量上。执行 with 块中的代
    # 码时,这个函数会在这一点暂停
    yield 'JABBERWOCKY'

    # 控制权一旦跳出 with 块,继续执行 yield 语句之后的代码;这里是恢复成原来的 sys.stdout.write 方法。
    sys.stdout.write = original_write


if __name__ == '__main__':
    with looking_glass() as what:
        print('what', what)
        # YKCOWREBBAJ

    print(what)
    # JABBERWOCKY


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值