如何让对象支持上下文协议

老样子,先抛出个问题,如何让对象支持上下文管理协议(context-management protocol,通过 with 语句触发)。

一, 回顾:with/as环境管理器

 with/as语句设计是作为try/finally的替代方案,就像try/finally语句一样,with/as语句也是用于定义必须执行的终止或者说是清理的行为,无论在此过程中是否发生异常!

但不同的是,with语句更加支持丰富的基于对象的协议,可以为代码块定义支持和离开的动作。

1 with open(r'C:\misc\data') as myfile:
2     for line in myfile:
3         print(line)
4 
5 ……
View Code

在这里,对于open的调用,会返回一个简单的对象,赋值给变量名myfile。我们可以用一般的文件工具来使用myfile,就此而言,文件迭代器会在for循环内逐行读取。

然而,此对象也支持with语句所使用的环境管理协议,在这个with语句执行后,环境管理制保证由myfile所引用的文件对象会自动关闭,即使处理该文件时,for 循环引发了异常也是如此。

二,疑问:了解环境管理协议

以下是with语句实际工作方式。

1,计算表达式,所得到的对象称为环境管理器,他必须有__enter__和__exit__方法。

2,环境变量管理器的__enter__方法会被调用。如果as子句存在,其返回值会赋值给as子句中的变量,否则直接丢弃。

3,代码块中嵌套的代码会执行。

4,如果with代码引发异常,__exit__(type, value, traceback)方法就会被调用(其中带有异常细节),这些也是由sys.exc_info返回相同值。

5,如果with代码块没有触发异常,__exit__方法依旧会被调用, 其中type, value以及traceback参数默认会议None传递。

三,回归:如何实现?

举个例子:

 1 from socket import socket, AF_INET, SOCK_STREAM
 2 
 3 class LazyConnection:
 4     def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
 5         self.address = address
 6         self.family = AF_INET
 7         self.type = SOCK_STREAM
 8         self.sock = None
 9 
10     def __enter__(self):
11         if self.sock is not None:
12             raise RuntimeError('Already connected')
13         self.sock = socket(self.family, self.type)
14         self.sock.connect(self.address)
15         return self.sock
16     
17     def __exit__(self, exc_ty, exc_val, tb):
18         self.sock.close()
19         self.sock = None
View Code

这个类的核心就是表示一条网络连接,但是实际上在初始状态下它并不会做任何的事情(比如,它不会建立一条连接),相反的,网络连接是通过with语句来建立和关闭的(这个就是上下文管理器的基本需求)。示例如下:

from functools import partial

conn = LazyConnetion(('www.python.org',80))
# Connection closed
with conn as s:
    # conn.__enter__() executes: connection open
    s.send(n'GET /index.html HTTP/1.0\r\n')
    s.send(n'Host www.python.org\r\n')
    s.send(b'\r\n')
    resp = b' '.join(iter(partial(s.recv, 8192), b' '))
    # conn.__exit__() executes: connection closed

四,讨论,思路过程,以及优化!

要编写上下文管理器,原则就是代码必须在由with语句定义的代码块中。当遇到with语句的时候,__enter__()首先被触发执行。__enter__()的返回值,都放置在由as限定的变量中,之后去执行with代码块的语句,最后__exit__()方法被触发执行清理工作。

这种形式的控制流与with语句块中发生了什么情况是没有关联的,出现异常时候也是如此。实际上, __exit__()方法的三个参数就包含了异常类型,值,对挂起异常的追溯(如果出现异常的话)。__exit__()方法可以选择某种方法来传递使用异常信息,或者什么也不做,直接返回一个None作为结果。如果__exit__()返回为TRUE,异常就会被清理干净,就好像什么也没有发生一样,而程序也就会立刻执行with语句中的代码块。

或许你已经发现了,这个类只能建立一个socket连接,那么尝试优化一下吧。

form socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = AF_INET
        self.type = SOCK_STREAM
        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()

# Example use

from functools import partial

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

with conn as s1:
    
    ……
    with conn as s2:
            ……
            # s1and s2 are independent sockets
View Code

各位看官请注意!学python面试的时候也会问及上下文管理机制实现原理

猜你喜欢

转载自www.cnblogs.com/zhoulixiansen/p/9130037.html