with语句上下文管理器

with语句上下文管理器

官方定义

with 语句用于包装带有使用上下文管理器定义的方法的代码块的执行。 这允许对普通的 try…except…finally 使用模式进行封装以方便地重用。关于 with语句 的详细介绍见 PEP-343

执行过程

with 语句的执行过程如下:

  1. The context expression (the expression given in the with_item) is evaluated to obtain a context manager.
  2. The context manager’s __exit__() is loaded for later use.
  3. The context manager’s __enter__() method is invoked.
  4. If a target was included in the with statement, the return value from __enter__() is assigned to it.
  5. The suite is executed.
  6. The context manager’s __exit__() method is invoked. If an exception caused the suite to be exited, its type, value, and traceback are passed as arguments to __exit__(). Otherwise, three None arguments are supplied.

在官方的中文文档中此部分翻译不是很精确,个人翻译如下(仅供参考)

  1. 使用 with语句 创建上下文管理器;
  2. 创建上下文管理器时,自动加载 __exit__() 方法,供结束时自动调用;
  3. 创建上下文管理器时,自动调用 __enter__() 方法;
  4. 如果将 with语句 创建的上下文管理器赋值给某个变量名,则这个变量名的值是 __enter__() 的返回值;
  5. 执行上下文管理器中的代码段;
  6. 调用上下文管理器的 __exit__() 方法,如果在执行第5步的过程中代码报错,则会将错误类型、错误信息、回溯信息作为参数传递给 __exit__() 方法,如未报错,则此三个参数均为 None 值。

__enter__

上下文管理器入口,with语句 会自动将 __enter__() 方法的返回值赋值给 as变量。如果仅定义此方法,未定义 __exit__() 方法,将抛出 AttributeError

__exit__

上下文管理器自动退出时执行的方法,如果仅定义此方法,未定义 __enter__() 方法,将抛出 AttributeError

示例

话说作者养了一只橘猫,取名“呆呆”,俗话说“十只橘猫九只胖,还有一只压倒炕”,从它日常的习惯来看,绝对有成为最独特那只的资质。不过它在吃饱睡足之余,也会有一些有趣的行为,无聊的时候,在家里上蹿下跳,并伴随无聊的喵喵叫,当陪它玩耍一会后,它就躺在我腿上安心睡觉了。

将猫咪每次闹腾的行为,抽象为 CatBehavior,如下示例,这样我们就可以使用 with语句 来管理猫咪单次活动的行为

class CatBehavior:

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

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

if __name__ == '__main__':
    with CatBehavior('Tom') as behavior:
        print(behavior)

上方示例中,输出的内容为 None,这是因为 __enter__() 方法无返回值,现在我们来修改代码

class CatBehavior:

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

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exc_type: ', exc_type)
        print('exc_val: ', exc_val)
        print('exc_tb: ', exc_tb)
        pass

    def boring(self):
        print(f'>>> {self.name}: Boring...')

    def jump(self):
        print(f'>>> {self.name}: Making noise...')

    def played(self):
        print(f'>>> {self.name}: Walk away after played...')

    def sleep(self):
        print(f'>>> {self.name}: Purr...purr...')

if __name__ == '__main__':
    with CatBehavior('Tom') as behavior:
        behavior.boring()
        behavior.jump()
        behavior.played()
        behavior.sleep()

上方代码从逻辑上来说没有问题,但是从 面向对象 的角度来看,是有优化的点,优化如下

class CatBehavior:

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

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exc_type: ', exc_type)
        print('exc_val: ', exc_val)
        print('exc_tb: ', exc_tb)
        pass

    def boring(self):
        print(f'>>> {self.name}: Boring...')
        return self

    def jump(self):
        print(f'>>> {self.name}: Making noise...')
        return self

    def played(self):
        print(f'>>> {self.name}: Walk away after played...')
        return self

    def sleep(self):
        print(f'>>> {self.name}: Purr...purr...')
        return self


if __name__ == '__main__':
    with CatBehavior('Tom') as behavior:
        behavior.boring().\
            jump().\
            played().\
            sleep()

在实例的方法中返回实例本身,是一个很容易忽略的技巧,当然肯定不是所有的业务场景都适用这个技巧,还需要根据实际情况来实现。请注意:截止到现在,__exit__() 方法中参数的值均为 None

现在我们在 boring() 方法中抛出异常,代码修改如下

class CatBehavior:

    # 仅修改boring方法,省略其他代码

    def boring(self):
        raise ValueError('Test error')

再次执行,我们会发现 __exit__() 方法中输出的内容如下,即代码中抛出的异常信息将作为参数传递给 __exit__() 方法,且程序报错退出

>>> exc_type:  <class 'ValueError'>
>>> exc_val:  Test error
>>> exc_tb:  <traceback object at 0x0000013BE3D40E48>

在上面示例中,__exit__() 方法没有返回值,如果在此方法中返回 True,报错信息将被忽略,修改 __exit__() 方法

允许对普通的 try…except…finally 使用模式进行封装以方便地重用

class CatBehavior:
    def __exit__(self, exc_type, exc_val, exc_tb):
        return True

再次执行,可以看到程序正常执行完成,并未因异常而退出。

总结

我们可以通过实现 __enter__()__exit__() 方法来让对象支持 with语句上下文管理器,此特性较多用于对资源进行访问的场合,如访问文件、访问数据库等,当然也可以根据实际业务情况来自定义应用场景。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值