contextlib

标签(空格分隔): python

contextlib 是一个上下文管理模块 那什么是上下文管理,什么时候需要用到上下文管理?

我的理解就是,比如你打开文件的时候,最终你需要改变这个打开的文件资源,如果在关闭文件资源之前,你的代码发生了错误,很有可能导致文件不会被关闭,这会导致“资源泄露”

在这种情况下,我们需要一个上下文管理器,保证无论代码是否出错,打开的'资源'最终都会被释放

所以,当你自定义了一个资源,那你就需要使用到contextclib去管理这个资源

with file('test,'r') as f:
    print f.readline()

# 代码执行完毕之后,不管是在哪种情况下,程序都会“自动关闭文件

使用with修辞,资源在引用完成之后,python会自动关闭这个资源 with 可以自动关闭打开的资源,但是,我们自定义的一些特殊资源,怎么关闭,我们可以这样做:

import contextlib

@contextlib.contextmanager
def session_stack():
    if not hasattr(db_ctx, 'session_stack'):
        db_ctx.session_stack = 0
    try:
        db_ctx.session_stack += 1
        yield # 注意yield的使用
    finally:
        db_ctx.session_stack -= 1

# 使用with调用这个函数
with session_stack():
    pass
# 这个函数首先会执行`yield`之前的代码,给资源`session_stack=1`, 然后会执行`session_stack -= 1`

with修饰一个可执行对象,我们需要理解什么是可执行对象,在python中,常见的有函数,类,类的实例化对象,类方法,实例化对象方法等,所以with可以修饰以上几类对象

with到底做了什么事情, 请看下面:


class Context(object):

    def __init__(self):
        print 'execute __init__ ...'

    def __enter__(self):
        print 'execute __enter__ ...'
        return 'hello'

    def __exit__(self, *args):
        print 'execute __exit__ ...'

# with 修饰的实例化对象
with Context() as e:
    print 'execute with code'
    # e就是__entry__函数的返回值
    print e

# 必须得搞清楚这段代码的执行过程:
# outputs:
# execute __init__ ...
# execute __enter__ ...
# execute with code
# hello
# execute __exit__ ...

通过以上代码可以发现,调用with的时候,实际上依次执行了被with修饰的对象的__init__, __entry__ __exit__ 函数

试想一下,我们可以实现这样一个类,在__entry__函数中打开资源, 在__exit__函数中关闭资源, 然后在用类去装饰某个函数,那么我们就可以用这个类去管理函数执行的上下文了

class Contextlib(object):
    def __init__(self, func):
        print 'init decoration class'
        # 生成generation
        self.gen = func()

    def __enter__(self):
        print 'excute code  before yield'
        self.gen.next()

    def __exit__(self, *agrs):
        print 'excute code after yield'
        try:
            self.gen.next()
        except (StopIteration):
            print 'done'
@Contextlib
def context_lib(*args):
    print 'outputs  before run into yield'
    yield 'yield return value'
    print 'outputs after excute yield'

# 请注意这个细节, 一个函数被类装饰之后,类型就变成一个类
print type(context_lib)
# outputs:
# <class '__main__.Contextlib'>

print type(context_lib())
# outputs:
# <type 'generator'>

# 注意context_lib被Contextlib对象装饰,返回的结果是Contextlib对象,所以会调用Contextlib的__entry__方法

# 不要使用with context_lib() as f , 因为context_lib()执行之后,返回的是一个generator,generator可没有__entry__方法,因而会报错
with context_lib as f:
    print f
# outputs:
# init decoration class
# excute code  before yield
# first called
# yield return value  "f" 就是yielf的返回值
# excute code after yield
# last called
# done

下面是python contextlib 的源代码:

class GeneratorContextManager(object):
    """Helper for @contextmanager decorator."""

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

    def __enter__(self):
        try:
            return self.gen.next()
        except StopIteration:
            raise RuntimeError("generator didn't yield")

    def __exit__(self, type, value, traceback):
        if type is None:
            try:
                self.gen.next()
            except StopIteration:
                return
            else:
                raise RuntimeError("generator didn't stop")
        else:
            if value is None:
                # Need to force instantiation so we can reliably
                # tell if we get the same exception back
                value = type()
            try:
                self.gen.throw(type, value, traceback)
                raise RuntimeError("generator didn't stop after throw()")
            except StopIteration, exc:
                # Suppress the exception *unless* it's the same exception that
                # was passed to throw().  This prevents a StopIteration
                # raised inside the "with" statement from being suppressed
                return exc is not value
            except:
                # only re-raise if it's *not* the exception that was
                # passed to throw(), because __exit__() must not raise
                # an exception unless __exit__() itself failed.  But throw()
                # has to raise the exception to signal propagation, so this
                # fixes the impedance mismatch between the throw() protocol
                # and the __exit__() protocol.
                #
                if sys.exc_info()[1] is not value:
                    raise


def contextmanager(func):

    @wraps(func)
    def helper(*args, **kwds):
        return GeneratorContextManager(func(*args, **kwds))
    return helper

至此,我们搞清楚了上下文的两个关键字:contextlib, with, 也知道了什么时候该使用上下文管理

results matching ""

    No results matching ""