• 上下文管理器
  • Python 中的上下文管理器装饰器

    上下文管理器

    一般的上下文管理器(原版)

    通常情况下,上下文管理器是这样的

    class MyResource:
        def __enter__(self):
            print('connect to resource')
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('close resource connection')
    
        def query(self):
            print('query data')
    
    with MyResource() as r:
        r.query()
    

    它的执行流程是:enter,返回实例对象,即我们的r,然后执行r.query(), 最后退出 执行exit方法。用一种函数表示就是这种样的:(显然这是行不通的)

    def make_myresource():
        print('connect to resource')
        return MyResource()# 跳出去执行查找方法
        print('close resource connection')
    
    with make_myresource() as r
        r.query()
    

    原因是return会中止程序,不再执行后面的代码,那么,如果我们使用的是yield呢?因为yield会保存状态,并且再下一次执行时接着从上一次执行的地方继续执行,我们改造一下:

    contextmanager

    # encoding:utf-8
    from contextlib import contextmanager
    
    
    class MyResource:
        def query(self):
            print('query data')
    
    
    @contextmanager
    def make_myresource():
        print('connect to resource')
        yield MyResource()
        print('close resource connection')
    
    with make_myresource() as r:
        r.query()
    

    执行

    connect to resource
    query data
    close resource connection
    

    应用

    改造print

    假设我们在打印一本书名是,想自动给书名前后加上书名号《》,类似这样的,即输出的结果是:《活着》

    with book_mark():
        print("《")
        print("活着")
        print("》")
    

    我们先用一般的上下文管理的方式实现:

    class BookMark:
        def __enter__(self):
            print("《", end='')
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("》", end='')
    
    with BookMark():
        print('活着',end='')
    

    现在我们来改造它:

    from contextlib import contextmanager
    @contextmanager
    def make_mark():
        print("《",end='')
        yield
        print("》",end='')
    
    with make_mark():
        print('活着', end='')
    

    简单总结一下:我们要编写的这个需要contextmanager装饰的函数中yield之前会执行enter中的操作,而yield之后 则是执行exit中的操作。而真正的动作则是在with语句中执行即可。

    伪代码:

    @contextmanager
    def func():
        enter (进入时的操作)
        yield  跳出执行我们的核心动作
        exit (退出前的操作
    
    with func as f:
         核心动作
    

    数据库提交

    下面看更实际的例子:在数据据库提交数据使用事务来保证它原子性操作,要怎么改造呢 未修改前:

    try:
        gift = Gift()
        gift.isbn = isbn
        gift.uid = current_user.id
        db.session.add(gift)
        db.session.commit()
    except Exception as e:
        db.session.rollback()
    

    改造后:

    from contextlib import contextmanager
    
    @contextmanager
    def auto_commit():
        try: #进入前的操作
            yield # 跳出执行核心动作
            self.session.commit() # 返回后要接着执行的动作
        except Exception as e:
            self.session.rollback()
            raise e
    
    with db.auto_commit():
        # 核心动作
        gift = Gift()
        gift.isbn = isbn
        gift.uid = current_user.id
        db.session.add(gift)
    

    总结: 在我们定定义的需要contextmanager装饰的函数中我们只需要写,前戏和事后回味两部分内容。这两部分内容用yield分隔开。 而在我们调用with语句中则是执行真正的战斗部分。

    那么这种装饰器有什么用呢? 例如:别人源码实现了某个类,你想改造成上下文管理器,通常情况下,你可以对该源码进行修改,实现enter,exit方法,但这样显示不够方便。简单的方法就是使用contextmanager装饰器,在yield处返回该类的实例对象,在with上下文语句中执行该类的实例方法即可。

    上一篇:Ruby selenium part10 选择

    下一篇:Join 命令