老样子,先抛出个问题,如何让对象支持上下文管理协议(context-management protocol,通过 with 语句触发)。
一, 回顾:with/as环境管理器
with/as语句设计是作为try/finally的替代方案,就像try/finally语句一样,with/as语句也是用于定义必须执行的终止或者说是清理的行为,无论在此过程中是否发生异常!
但不同的是,with语句更加支持丰富的基于对象的协议,可以为代码块定义支持和离开的动作。
1 with open(r'C:miscdata') as myfile: 2 for line in myfile: 3 print(line) 4 5 ……
在这里,对于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
这个类的核心就是表示一条网络连接,但是实际上在初始状态下它并不会做任何的事情(比如,它不会建立一条连接),相反的,网络连接是通过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 ') s.send(n'Host www.python.org ') s.send(b' ') 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
各位看官请注意!学python面试的时候也会问及上下文管理机制实现原理!