python:上下文管理(__enter__()、__exit__() 、with语句)contextlib简化上下文管理器

With语句是什么?

有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。

如果不用with语句,代码如下:

file = open("/tmp/foo.txt")
data = file.read()
file.close()


这里有两个问题。一是可能忘记关闭文件句柄;二是文件读取数据发生异常,没有进行任何处理。下面是处理异常的加强版本:

file = open("/tmp/foo.txt")
try:
    data = file.read()
finally:
    file.close()


虽然这段代码运行良好,但是太冗长了。这时候就是with一展身手的时候了。除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。下面是with版本的代码:

with open("/tmp /foo.txt") as file:
    data = file.read()


with如何工作?

这看起来充满魔法,但不仅仅是魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。

紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。

下面例子可以具体说明with如何工作:
 

class Query(object):

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('Begin')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print('exc_type:',exc_type,"\nexc_value:",exc_value,"\ntraceback:",traceback)
        if exc_type:
            print('Error:%s'%exc_type)
        else:
            print('End')
        return True

    def query(self):
        print('Query info about %s...' % self.name)


with Query('Bob') as q:
    q.query()

输出如下

Begin
Query info about Bob...
exc_type: None 
exc_value: None 
traceback: None
End

with Query('Bob') as q:

1 with Query('Bob') >> 触发__enter__(),拿到返回值

2.as q ----> q=返回值

with Query('Bob') as q  等同于 q=  Query('Bob').__enter__()

4.执行代码块

①,没有异常情况下,整个代码块运行完成后触发__exit__,三个返回值都为None

②,有异常情况下,从异常出现的位置触发__eixt__

     a.如果__exit__的返回值为True,代表吞吐了异常

     b. 如果__exit__的返回值不为True,代表吐出了异常

     c.__exit__的运行完成就代表了整个with语句的执行完毕

class Query(object):

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('Begin')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print('exc_type:',exc_type,"\nexc_value:",exc_value,"\ntraceback:",traceback)
        if exc_type:
            print('Error:%s'%exc_type)
        else:
            print('End')
        return True

    def query(self):
        q2
        print('Query info about %s...' % self.name)


with Query('Bob') as q:
    q.query()


## 运行:
Begin
exc_type: <class 'NameError'> 
exc_value: name 'q2' is not defined 
traceback: <traceback object at 0x0000010CC4F8D648>
Error:<class 'NameError'>

去掉  return True 报错

Begin
Traceback (most recent call last):
exc_type: <class 'NameError'> 
  File "F:/django/alipay/check/322.py", line 49, in <module>
exc_value: name 'q2' is not defined 
    q.query()
traceback: <traceback object at 0x0000028EB2C4D648>
  File "F:/django/alipay/check/322.py", line 44, in query
Error:<class 'NameError'>
    q2
NameError: name 'q2' is not defined

@contextmanager

编写__enter__和__exit__仍然很繁琐,因此Python的标准库contextlib提供了更简单的写法,上面的代码可以改写如下:

from contextlib import contextmanager

class Query(object):

    def __init__(self, name):
        self.name = name

    def query(self):
        print('Query info about %s...' % self.name)

@contextmanager
def create_query(name):
    print('Begin')
    q = Query(name)
    yield q
    print('End')

@contextmanager这个decorator接受一个generator,用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了:

with create_query('Bob') as q: q.query()

很多时候,我们希望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现。例如:

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

with tag("h1"):
    print("hello")
    print("world")

上述代码执行结果为:
<h1>
hello
world
</h1>

代码的执行顺序是:

  1. with语句首先执行yield之前的语句,因此打印出<h1>;
  2. yield调用会执行with语句内部的所有语句,因此打印出hello和world;
  3. 最后执行yield之后的语句,打印出</h1>。

因此,@contextmanager让我们通过编写generator来简化上下文管理。

发布了73 篇原创文章 · 获赞 41 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/weixin_37989267/article/details/105628261
今日推荐