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()

 

原文地址:https://www.cnblogs.com/zhongguiyao/p/11048284.html