with and contextmanager

with

初识with

在Python中,读写文件这样的资源要特别注意,必须在使用完毕后正确关闭它们。正确关闭文件资源的一个方法是使用try...finally

try:
    f = open("/directory/filename", 'r')
    f.read()
finally:
      # 如果f不存在, 则应该是文件对象未打开
    if f:
        f.close()

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

# 注意f只会在with语句块有效
with open("/directory/filename", 'r') as f:
        f.read()

with的工作原理

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

实现上下文管理是通过__enter____exit__这两个方法实现的. 我们可以简单通过一个例子来说明with的内部调用方法

class Generator(object):

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

    def __enter__(self):
        print("There's no way out")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
          # 检查退出的类型
        # 当有异常发生时 exc_type存在值
        if exc_type:
            print('Open the door')
        else:
            print('100%')

    def generate(self):
        print('%s can uses love to generate electricity' % self.name)

我们可以通过以下代码调用

# 紧跟with后面的语句被求值后,返回对象的 `__enter__() `方法被调用,
# 这个方法的返回值将被赋值给as后面的变量    
with Generator("LEX") as lex:
      lex.generate()
# 当with后面的代码块全部被执行完之后,将调用返回对象的 `__exit__()`方法。
用爱发电

其实 with 就是这么简单, 大部分初学者只要理解到这里就可以了, 所以小白勿入, 高能级别

高能预警

上下文管理器

我知道有很多小白还是读了, 那我先介绍几个名词让你们知男而退

  • 上下文管理协议(Context Management Protocol):包含方法 __enter____exit__,支持

该协议的对象要实现这两个方法。

  • 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了

__enter____exit__方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,

负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,

也可以通过直接调用其方法来使用。

  • 运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__

__exit__ 方法实现,__enter__ 方法在语句体执行之前进入运行时上下文,__exit__

语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。

  • 上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式

要返回一个上下文管理器对象。如 例子with后面的Generator("LEX") as lex:

  • 语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管

理器的__enter__方法,执行完语句体之后会执行 __exit__ 方法。

其实这些术语不需要你们立刻理解, 只需要在以后的课程或者项目中慢慢体会.

@contextmanager

编写__enter____exit__仍然很繁琐,因此Python的标准库contextlib提供了更简单的写法, 让我们更加简洁的定制自己的上下文管理器. 同样的爱之代码走起:

from context import contextmanager

class Generator(object):

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

    def generate(self):
        print('%s can uses love to generate electricity' % self.name)

@contextamnager
def create_generator(name):
      print("I'm generating electricity from love again")
      lex = Generator(name)
    # 返回给with...as...语句
    yield lex
    print('100%')

@contextmanager这个decorator必须接受一个 ==generator== ,用yield语句把with ... as var把变量输出出去, 然后属于你的上下文管理器就弄了, 是不是出奇的简单

好久没有这么简单

使用一下这个管理器, 说说感觉

with create_generator("LEX") as lex:
      lex.generate()
感觉良好

@closing

但是少年郎们还没结束呢, 让我们了解一下closing 的作用, 它的作用就是让不是上下文管理器的对象转化为上下文管理器, 即可使用with语句

urllib.request import urlopen

with closing(urlopen('https://www.bilibili.com')) as page:
    for line in page:
        print(line)

其实closing内部非常好实现, 内部还是调用了contextmanager方法

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

closing 不同于 @contextmanager, 他的本身不需要生成器, 可谓加上这行代码, 秒变真男人

总结

  1. with 关联着对象的__exit____enter__方法, 你必须清楚他们是何时被调用, 他们的返回值又返回到哪里去了

  2. @contextmanager 所装饰的对象必须是带yield的生成器, yield 会把后面的值返回给with...as...语句

  3. @closing 原先必许在所装饰的对象定义一个close方法, 否则会报错, 但是现在不会, 所以这种一行真男人得灵药不多了

  4. 上下文管理器将会贯穿大型框架结构, 如flask的 Application上下文, 请求(request)上下文等等, 学习上下文管理器可以帮助我们更好理解那些框架的工作原理.

未完待续...

引用

廖雪峰 contextlib

你可能感兴趣的:(with and contextmanager)