zoukankan      html  css  js  c++  java
  • 13.上下文管理

    六、上下文管理

    上下文管理可以包装任意格式的代码块

    上下文管理的语法

    上下文管理器是一个包装任意代码块的对象。上下文管理器保证进入上下文管理器时,每次代码执行的一致性;当退出上下文管理器时,

    相关的资源会被正确回收。

    值得注意的是,上下文管理器一定能够保证退出步骤的执行。如果进入上下文管理器,根据定义,一定会有退出步骤。即使内部代码抛出了异常,这点也成立。事实上,如果退出步骤处理异常合适,那么上下文管理器的退出代码为处理这类异常提供了一个机会(虽然不强制要求)。

    因此上下文管理器的功能类似于执行try、except和finally关键字。通常,这也是一种封装需要被重复使用的try-except-finally结构的有效机制。

     上下文管理器的任务是:代码块执行前准备,代码块执行后收拾

     with子句

    1、当 with 语句执行时,便执行上下文符号(译者注:就是 with 与 as 间内容)来获得一个上下文管理器.

    2、上下文管理器的职责是提供一个上下文对象.这是通过调用__context__()方法来实现的 。该方法返回一个上下文对象,用于在 with 语句块中处理细节

    3、需要注意的是上下文对象本身就可以是上下文管理器.所以 context_expr 既可以为一个真正的上下文管理器,也可以是一个可以自我管理的上下文对象

    4、一旦我们获得了上下文对象,就会调用它的__enter()__方法

    5、在将打开文件的操作放在with语句中,代码块结束后,文件将自动关闭

    6、上下文管理器比文件自身多了一个异常处理功能,它允许我们把文件处理代码包装到一个逻辑层中,以确保在退出后可以自动关闭文件,

    而不是依赖于垃圾收集上的自动关闭

    7、with 语法的基本用法看上去如下:

    with context_expr [as var]:
      with_suite

    例子:

    >>> with open('foo.py') as f:
    ... data = f.readlines()
    ...
    >>> f.closed
    True 

     8、它仅能工作于支持上下文管理协议(context management protocol)的对象.这显然意味着只有内建了"上下文管理"的对象可以和 with 一起工作

     

    如何使用上下文管理器:

    如何打开一个文件,并写入"hello world"

    filename="my.txt"
    mode="w"
    f=open(filename,mode)
    f.write("hello world")
    f.close()

    当发生异常时(如磁盘写满),就没有机会执行第5行。当然,我们可以采用try-finally语句块进行包装:

    writer=open(filename,mode)
    try:
        writer.write("hello world")
    finally:
        writer.close()

    当我们进行复杂的操作时,try-finally语句就会变得丑陋,采用with语句重写:

    with open(filename,mode) as writer:
        writer.write("hello world")

    as指代了从open()函数返回的内容,并把它赋给了新值。with完成了try-finally的任务。

    自定义上下文管理器  

    with语句的作用类似于try-finally,提供一种上下文机制。要应用with语句的类,其内部必须提供两个内置函数__enter__和__exit__。前者在主体代码执行前执行,后者在主体代码执行后执行。as后面的变量,是在__enter__函数中返回的。

    #!/usr/bin/env python
    #coding:utf8
    class echo():
        def output(self):
            print "hello world"
        def __enter__(self):
            print "enter"
            return self  #可以返回任何希望返回的东西
        def __exit__(self,exception_type,value,trackback):
            print "exit"
            if exception_type==ValueError:
                return True
            else:
                return False
    
    echo = echo()
    with echo as e:
        e.output()

    执行结果:

    enter
    hello world
    exit

    enter和exit方法

    1.with语句的表达式的作用是返回一个遵循特定协议的对象。具体来说,该对象必须定义一个__enter__方法和一个__exit__方法,且后者必须接受特定参数

    2.除了传统的self参数,__enter__方法不接受任何其他参数。当对象返回时该方法立即执行,然后如果有as变量(as子句是可选项),返回值将被赋给as后面使用的变量。

    3.一般来说,__enter__方法负责执行一些配置。另外一方面,__exit__方法带有3个位置参数(不包括传统的self参数):一个异常类型、一个异常实例和一个回溯。如果没有异常,这3个参数全被设置为None,但如果在代码块内有异常发生,则参数被填充。

    #!/usr/bin/env python
    #coding:utf-8
       
    class ContextManager(object):
        def __init__(self):
            self.entered = False
    
        def __enter__(self):
            self.entered = True
            return self
    
        def __exit__(self, exc_type, exc_instance,traceback):
            self.entered = False
    
    cm = ContextManager()
    print cm.entered

    执行结果:

    False

    该上下文管理器只是返回自身和设置其entered变量,在进入时设置为True,退出时设置为False。

    如果相同的ContextManager实例作为上下文管理器,观察它的entered属性会先变成True,然后在退出时再次变成False

    #!/usr/bin/env python
    #coding:utf-8
       
    class ContextManager(object):
        def __init__(self):
            self.entered = False
    
        def __enter__(self):
            self.entered = True
            return self
    
        def __exit__(self, exc_type, exc_instance,traceback):
            self.entered = False
    
    
    cm = ContextManager()
    with cm:
         print cm.entered
    print cm.entered 

    执行结果:

    True
    False

    如果在其他地方不需要ContextManager实例,可以用with语句将其实例化。该方法可行的原因在于它的__enter__方法只返回了它本身。

    #!/usr/bin/env python
    #coding:utf-8
       
    class ContextManager(object):
        def __init__(self):
            self.entered = False
    
        def __enter__(self):
            self.entered = True
            return self
    
        def __exit__(self, exc_type, exc_instance,traceback):
            self.entered = False
    
    
    # cm = ContextManager()
    
    with ContextManager() as cm:
         print cm.entered
    
    # print cm.entered

    执行结果:

    True

    异常处理

    1.上下文管理器必须定义__exit__方法,该方法可以选择性地处理包装代码快中出现的异常,或处理其他需要关闭上下文管理状态的事情。

    2.__exit__方法必须定义3个位置参数:异常类型、异常实例以及回溯选择。如果上下文管理器中的代码没有发生异常,则所有3个参数的值为None。

    3.如果__exit__方法接收一个异常,就有处理这个异常的义务。从根本上讲,这个方法有3个可选项:

    可以传播异常(因为会在__exit__完成后再次抛出异常)
    可以终止异常
    可以抛出不同的异常

    4.可以通过让一个__exit__方法返回False实现异常的传播,或者通过让__exit__返回True终止异常。另外,如果__exit__抛出不同的异常,它将代替异常被发送出去。

    何时编写上下文管理器

    涉及确保某种资源以一种期望的方法被初始化或反初始化,或尽力去避免重复时可以使用上下文管理器

    避免重复

    当提到避免重复时,异常处理是最为常见的。上下文管理器能够传播和终止异常,这使得最适合将它与except子句放在同一个地方定义。

    1.传播异常

    __exit__方法只是向流程链上传播异常,这是通过返回False实现的,根本不需要与异常实例交互

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    class BubbleExceptions(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_val:
                print "Bubbling up  exception: %s." % exc_val
            return False
    
    with BubbleExceptions():
        print 5 + 5

    执行结果:

    10

    在上下文管理器中运行普通代码块(不抛出异常)将不会做什么特殊的事情

    例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    class BubbleExceptions(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_val:
                print "Bubbling up  exception: %s." % exc_val
            return False
    
    with BubbleExceptions():
        print 5 / 0

    执行结果:

    Bubbling up  exception: integer division or modulo by zero.
    Traceback (most recent call last):
      File "G:/PycharmProject/fullstack2/week1/test6.py", line 15, in <module>
        print 5 / 0
    ZeroDivisionError: integer division or modulo by zero


    这里有2件需要注意的事情:

    1.第一行(Bubbling up  exception: integer division or modulo by zero.)由__exit__方法自身产生。它对应于__exit__方法中的print语句。
    这意味着__exit__方法的确运行了且已完成。
    2.因为该方法返回了False,所以被首先发送给__exit__的异常只是被重新抛出了。

    2.终止异常

    __exit__方法拥有的另一个选项就是终止它所接收的异常。下面的上下文管理终止所有可能发送给__exit__方法的异常(但是永远不要这样做):

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    class SuppressExceptions(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_val:
                print "SuppressException: %s." % exc_val
    
            return True
    
    
    with SuppressExceptions():
        print 5+5

    执行结果:

    10

    例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    class SuppressExceptions(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_val:
                print "SuppressException: %s." % exc_val
    
            return True
    
    
    with SuppressExceptions():
        print 5 / 0

    执行结果:

    SuppressException: integer division or modulo by zero.

    注意:

    1.首先且最明显的是回溯消失了。由于异常被__exit__方法处理了(终止),因此程序没有引发异常,继续执行。
    2.第二个要注意的是没有任何返回值。当进入解释器时,尽管表达式 5 + 5返回了10,但引发异常的表达式 5/0根本不会显示值。异常在计算该值的过程中
    引发,从而触发__exit__的运行。实际上永远不会返回任何值。另外,值得注意的是任何出现在5/0后面的代码都不会再执行。

    3.处理特定异常类

    一个简单的异常处理函数__exit__可以仅检查异常是否是特定异常类的实例,执行任何必要的异常处理,并根据是否获得其他类型的异常类返回True(或返回False)。

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    class HandleValueError(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if not exc_type:
                return True
    
            if issubclass(exc_type,ValueError):
                print "Handling ValueError: %s" % exc_val
                return True
    
            return False
    
    
    with HandleValueError(): #使用该上下文管理器并且在代码块内引发ValueError,则会看到想要的输出,之后异常终止
        raise ValueError("Wrong value.")

    执行结果:

    Handling ValueError: Wrong value.

    例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    class HandleValueError(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if not exc_type:
                return True
    
            if issubclass(exc_type,ValueError):
                print "Handling ValueError: %s" % exc_val
                return True
    
            return False
    
    
    with HandleValueError():  #使用上下文管理器但引发不同类的异常,那么抛出该异常并输出回溯
        raise TypeError("Wrong type.")

    执行结果:

    Traceback (most recent call last):
      File "G:/PycharmProject/fullstack2/week1/test6.py", line 21, in <module>
        raise TypeError("Wrong type.")
    TypeError: Wrong type.

    4.不包括的子类

    如何完成类或实例的检查也可以更加灵活。假如想要捕获一个给定的异常类,但不希望显式地捕获它的子类。在传统的except代码块中不能这样做,也不该这样做。但是上下文管理器就能处理这样的极端情况。

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    class ValueErrorSubclass(ValueError):
        pass
    
    
    class HandleValueError(object):
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if not exc_type:
                return True
    
            if exc_type == ValueError:#通过使用==来检查其类型,这意味着对ValueError的处理与之前一样,只是管理器不再处理ValueError的子类
                print "Handling ValueError: %s " % exc_val
                return True
    
            return False
    
    
    with HandleValueError():
        raise ValueErrorSubclass("foo bar baz")

    执行结果:

    Traceback (most recent call last):
      File "G:/PycharmProject/fullstack2/week1/test6.py", line 25, in <module>
        raise ValueErrorSubclass("foo bar baz")
    __main__.ValueErrorSubclass: foo bar baz

    5.基于属性的异常处理

    上下文管理器可以根据异常的类型来决定是否处理异常,与此类似,它还可以根据异常的属性来决定是否处理异常

    更简单的语法:contextlib模块

    contextlib模块的作用是提供更易用的上下文管理器,它是通过Generator实现的。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制,常用框架如下:

    from contextlib import contextmanager
    
    
    @contextmanager
    def make_context():
        print 'enter'
        try:
            yield "ok"
        except RuntimeError, err:
            print 'error', err
        finally:
            print 'exit'
    
    with make_context() as value:
        print value

    输出为:

        enter
        ok
        exit

    其中,yield写入try-finally中是为了保证异常安全(能处理异常)as后的变量的值是由yield返回。yield前面的语句可看作代码块执行前操作,yield之后的操作可以看作在__exit__函数中的操作。

    以线程锁为例:

    from contextlib import contextmanager
    import threading
    
    lock = threading.Lock()
    
    @contextmanager
    def loudLock():
        print('Locking')
        lock.acquire()
        yield
        print('Releasing')
        lock.release()
    
    with loudLock():
        print('Lock is locked: %s' % lock.locked())
        print('Doing something that needs locking')

    执行结果:

    Locking
    Lock is locked: True
    Doing something that needs locking
    Releasing

    contextlib.nested:减少嵌套

    对于:

    with open(filename,mode) as reader:
        with open(filename1,mode1) as writer:
            writer.write(reader.read())

    可以通过contextlib.nested进行简化:

    with contextlib.nested(open(filename,mode),open(filename1,mode1)) as (reader,writer):
        writer.write(reader.read())

    在python 2.7及以后,被一种新的语法取代:

    with open(filename,mode) as reader,open(filename1,mode1) as writer:
        writer.write(reader.read())

    contextlib.closing() 

    file类直接支持上下文管理器API,但有些表示打开句柄的对象并不支持,如urllib.urlopen()返回的对象。还有些遗留类,使用close()方法而不支持上下文管理器API。为了确保关闭句柄,需要使用closing()为它创建一个上下文管理器(调用类的close方法)。

    #!/usr/bin/env python
    #coding:utf8
    import contextlib
    
    
    class myclass():
        def __init__(self):
            print '__init__'
    
        def close(self):
            print 'close()'
    
    
    with contextlib.closing(myclass()):
        print 'ok'

    输出:

    __init__
    ok
    close()

     

  • 相关阅读:
    主线程等待子线程结束再做响应
    前端开发注意细节
    XSS攻击前端需注意
    移动端开发碰到一个坑
    连续改变Chrome浏览器窗口大小,可以导致内存泄漏
    js中使用使用原型(prototype)定义方法的好处
    父节点使用css的transform: translate(0, 0)时position:fixed在chrome浏览器中无效
    CSS 中的 em单位
    观察者模式和发布/订阅模式的区别
    搜索练习4
  • 原文地址:https://www.cnblogs.com/zhongguiyao/p/11048284.html
Copyright © 2011-2022 走看看