当前位置:   article > 正文

【Python系列专栏】第四十二篇 Python中常用内建模块(contextlib)_python contextlib

python contextlib

contextlib

引言

在Python中,读写文件要注意使用完毕后必须进行关闭(文件对象占用大量资源并且同一时间操作系统只能打开有限数量的文件。之前已经介绍了利用 try...finally 机制关闭文件资源的方法:

try:
    f = open('/path/to/file', 'r')
    f.read()
finally:
    if f:
        f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

但是,写 try...finally 非常繁琐,所以后续又介绍了使用 with 语句的方法。with 语句允许我们非常方便地使用资源,而不必担心资源没有关闭。使用 with 语句改写后,上面的代码就可以简化为:

with open('/path/to/file', 'r') as f:
    f.read()
  • 1
  • 2

事实上,并不是只有 open() 函数返回的文件对象才能使用 with 语句。任何对象,只要正确实现了上下文管理,就可以用于 with 语句


上下文管理的实现

上下文管理是通过 __enter____exit__ 这两个方法实现的。下面的类就实现了这两个方法:

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):
        if exc_type:
            print('Error')
        else:
            print('End')

    def query(self):
        print('Query info about %s...' % self.name)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这样我们就可以把自己写的资源对象用于 with 语句:

with Query('Bob') as q:
    q.query()
  • 1
  • 2

@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')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

简单解析一下,我们定义一个简单的 Query 类,只有一个 query() 方法。同时我们定义了一个 create_query() 函数,由于这个函数包含 yield 关键字,所以实际上它是一个生成器。不过这个生成器只生成和抛出一个 Query 类的对象。

@contextmanager 这个装饰器接收一个生成器,并为生成器抛出的对象添加上下文管理的功能。这样 with 语句就可以正常地工作了:

with create_query('Bob') as q:
    q.query()
  • 1
  • 2

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

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

with tag("h1"):
    print("hello")
    print("world")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述代码执行结果为:

<h1>
hello
world
</h1>
  • 1
  • 2
  • 3
  • 4

代码的执行顺序是:

  • with 语句首先执行 yield 前面的语句,因此打印出 <h1>
  • yield 之后会跳出生成器(tag() 函数),执行 with 语句内部的所有语句,因此打印出 helloworld
  • 执行完 with 语句内部的所有语句继续回到生成器;
  • 执行 yield 后面的语句,打印出 </h1>
  • 此时生成器所有语句执行完毕,不再生成,结束上下文。

借助 @contextmanager 装饰器,我们能够更加方便地实现上下文管理。


closing函数

前面一节介绍了如何为一个对象实现上下文管理功能,使得它能被作用于 with 语句。但是,得自己编写一个生成器还是很麻烦!有没有更更方便的办法呢?有!我们可以用 closing() 方法!

closing() 的本质如下:

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其实它就是一个经过 @contextmanager 装饰的生成器,它的作用就是把任意对象变为上下文对象,使其支持 with 语句。

再改写一次上面 Query 的例子:

from contextlib import closing

class Query(object):
    def __init__(self, name):
        self.name = name
    def query(self):
        print('Query info about %s...' % self.name)

with closing(Query('Bob')) as q:
    q.query()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这次更加简单了~

@contextlib 还有一些其他装饰器,可以帮助我们编写更简洁的代码。



声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/130297
推荐阅读
相关标签
  

闽ICP备14008679号