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


    老样子,先抛出个问题,如何让对象支持上下文管理协议(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 ……
    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
    ')
        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
    View Code

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

    没有过不去的坎,只有没加够的油!
  • 相关阅读:
    辅助性的“比较操作符”
    辅助性的“比较操作符”
    浙江一乘客没赶上火车退票不成把票撕了 结果"悲剧"了
    美国超震撼短片-梦想
    在HTML文件中加入空格
    揭秘人造肉
    不能发布网站简讯
    KMPlayer
    文件四处盖章签字等
    冬天到了如何御寒
  • 原文地址:https://www.cnblogs.com/zhoulixiansen/p/9130037.html
Copyright © 2020-2023  润新知