PyTips 0x0d - Python 上下文管理器

项目地址:https://git.io/pytipshtml

Python 2.5 引入了 with 语句(PEP 343)与上下文管理器类型(Context Manager Types),其主要做用包括:python

保存、重置各类全局状态,锁住或解锁资源,关闭打开的文件等。With Statement Context Managersgit

一种最广泛的用法是对文件的操做:github

with open("utf8.txt", "r") as f:
    print(f.read())
你好,世界!

上面的例子也能够用 try...finally... 实现,它们的效果是相同(或者说上下文管理器就是封装、简化了错误捕捉的过程):spa

try:
    f = open("utf8.txt", "r")
    print(f.read())
finally:
    f.close()
你好,世界!

除了文件对象以外,咱们也能够本身建立上下文管理器,与 0x01 中介绍的迭代器相似,只要定义了 __enter__()__exit__() 方法就成为了上下文管理器类型。with 语句的执行过程以下:code

  1. 执行 with 后的语句获取上下文管理器,例如 open('utf8.txt', 'r') 就是返回一个 file objectorm

  2. 加载 __exit__() 方法备用;htm

  3. 执行 __enter__(),该方法的返回值将传递给 as 后的变量(若是有的话);对象

  4. 执行 with 语法块的子句;ip

  5. 执行 __exit__() 方法,若是 with 语法块子句中出现异常,将会传递 type, value, traceback__exit__(),不然将默认为 None;若是 __exit__() 方法返回 False,将会抛出异常给外层处理;若是返回 True,则忽略异常。

了解了 with 语句的执行过程,咱们能够编写本身的上下文管理器。假设咱们须要一个引用计数器,而出于某些特殊的缘由须要多个计数器共享全局状态而且能够相互影响,并且在计数器使用完毕以后须要恢复初始的全局状态:

_G = {"counter": 99, "user": "admin"}

class Refs():
    def __init__(self, name = None):
        self.name = name
        self._G = _G
        self.init = self._G['counter']
    def __enter__(self):
        return self
    def __exit__(self, *args):
        self._G["counter"] = self.init
        return False
    def acc(self, n = 1):
        self._G["counter"] += n
    def dec(self, n = 1):
        self._G["counter"] -= n
    def __str__(self):
        return "COUNTER #{name}: {counter}".format(**self._G, name=self.name)
        
with Refs("ref1") as ref1, Refs("ref2") as ref2: # Python 3.1 加入了多个并列上下文管理器
    for _ in range(3):
        ref1.dec()
        print(ref1)
        ref2.acc(2)
        print(ref2)
print(_G)
COUNTER #ref1: 98
COUNTER #ref2: 100
COUNTER #ref1: 99
COUNTER #ref2: 101
COUNTER #ref1: 100
COUNTER #ref2: 102
{'user': 'admin', 'counter': 99}

上面的例子很别扭可是能够很好地说明 with 语句的执行顺序,只是每次定义两个方法看起来并非很简洁,一如既往地,Python 提供了 @contextlib.contextmanager + generator 的方式来简化这一过程(正如 0x01yield 简化迭代器同样):

from contextlib import contextmanager as cm
_G = {"counter": 99, "user": "admin"}

@cm
def ref():
    counter = _G["counter"]
    yield _G
    _G["counter"] = counter

with ref() as r1, ref() as r2:
    for _ in range(3):
        r1["counter"] -= 1
        print("COUNTER #ref1: {}".format(_G["counter"]))
        r2["counter"] += 2
        print("COUNTER #ref2: {}".format(_G["counter"]))
print("*"*20)
print(_G)
COUNTER #ref1: 98
COUNTER #ref2: 100
COUNTER #ref1: 99
COUNTER #ref2: 101
COUNTER #ref1: 100
COUNTER #ref2: 102
********************
{'user': 'admin', 'counter': 99}

这里对生成器的要求是必须只能返回一个值(只有一次 yield),返回的值至关于 __enter__() 的返回值;而 yield 后的语句至关于 __exit__()

生成器的写法更简洁,适合快速生成一个简单的上下文管理器。

除了上面两种方式,Python 3.2 中新增了 contextlib.ContextDecorator,能够容许咱们本身在 class 层面定义新的”上下文管理修饰器“,有兴趣能够到官方文档查看。至少在我目前看来好像并无带来更多方便(除了能够省掉一层缩进以外:()。

上下文管理器的概念与修饰器有不少类似之处,可是要记住的是 with 语句的目的是为了更优雅地收拾残局而不是替代 try...finally...,毕竟在 The Zen of Python 中,

Explicit is better than implicit.

Simple is better than complex.

更重要:P。


欢迎关注公众号 PyHub!

欢迎关注公众号 PyHub!

相关文章
相关标签/搜索