详解Python中contextlib上下文管理模块的用法

421次阅读  |  发布于5年以前

咱们用的os模块,读取文件的时候,其实他是含有enter exit 。 一个是with触发的时候,一个是退出的时候。


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

那咱们自己再实现一个标准的可以with的类。 我个人写python的时候,喜欢针对一些需要有关闭逻辑的代码,构造成with的模式 。


    #encoding:utf-8
    class echo:
      def __enter__(self):
        print 'enter'

      def __exit__(self,*args):
        print 'exit'

    with echo() as e:
      print 'nima'

contextlib是个比with优美的东西,也是提供上下文机制的模块,它是通过Generator装饰器实现的,不再是采用enterexit。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制。


    from contextlib import contextmanager

    @contextmanager
    def make_context() :
      print 'enter'
      try :
        yield {}
      except RuntimeError, err :
        print 'error' , err
      finally :
        print 'exit'

    with make_context() as value :
      print value

我这里再贴下我上次写的redis分布式锁代码中有关于contextlib的用法。其实乍一看,用了with和contextlib麻烦了,但是最少让你的主体代码更加鲜明了。


    from contextlib import contextmanager
    from random import random

    DEFAULT_EXPIRES = 15
    DEFAULT_RETRIES = 5

    @contextmanager
    def dist_lock(key, client):
      key = 'lock_%s' % key

      try:
        _acquire_lock(key, client)
        yield
      finally:
        _release_lock(key, client)

    def _acquire_lock(key, client):
      for i in xrange(0, DEFAULT_RETRIES):
        get_stored = client.get(key)
        if get_stored:
          sleep_time = (((i+1)*random()) + 2**i) / 2.5
          print 'Sleeipng for %s' % (sleep_time)
          time.sleep(sleep_time)
        else:
          stored = client.set(key, 1)
          client.expire(key,DEFAULT_EXPIRES)
          return
      raise Exception('Could not acquire lock for %s' % key)

    def _release_lock(key, client):
      client.delete(key)

Context Manager API

一个上下文管理器通过with声明激活, 而且API包含两个方法。enter()方法运行执行流进入到with代码块内。他返回一个对象共上下文使用。当执行流离开with块时,exit()方法上下文管理器清除任何资源被使用。


    class Context(object):

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

      def __enter__(self):
        print '__enter__()'
        return self

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

    with Context():
      print 'Doing work in the context.'

打印结果


    __init__()
    __enter__()
    Doing work in the context.
    __exit__()

执行上下文管理器时会调用enter离开时调用exit

enter能返回任意对象,联合一个指定名字于with声明。


    class WithinContext(object):

      def __init__(self, context):
        print 'WithinContext.__init__(%s)' % context

      def do_something(self):
        print 'WithinContext.do_something()'

      def __del__(self):
        print 'WithinContext.__del__'


    class Context(object):

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

      def __enter__(self):
        print '__enter__()'
        return WithinContext(self)

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

    with Context() as c:
      c.do_something()

打印结果


    __init__()
    __enter__()
    WithinContext.__init__(<__main__.Context object at 0x7f579d8e4890>)
    WithinContext.do_something()
    __exit__()
    WithinContext.__del__

如果上下文管理器能处理异常,exit()应该返回一个True值表明这个异常不需要传播,返回False异常会在执行exit之后被引起。


    class Context(object):

      def __init__(self, handle_error):
        print '__init__(%s)' % handle_error
        self.handle_error = handle_error

      def __enter__(self):
        print '__enter__()'
        return self

      def __exit__(self, exc_type, exc_val, exc_tb):
        print '__exit__(%s, %s, %s)' % (exc_type, exc_val, exc_tb)
        return self.handle_error

    with Context(True):
      raise RuntimeError('error message handled')

    print

    with Context(False):
      raise RuntimeError('error message propagated')

打印结果


    __init__(True)
    __enter__()
    __exit__(<type 'exceptions.RuntimeError'>, error message handled, <traceback object at 0x7fdfb32f8b00>)

    __init__(False)
    __enter__()
    __exit__(<type 'exceptions.RuntimeError'>, error message propagated, <traceback object at 0x7fdfb32f8b90>)
    Traceback (most recent call last):
     File "test.py", line 23, in <module>
       raise RuntimeError('error message propagated')
       RuntimeError: error message propagated

从生成器到上下文管理器

创建上下文管理的传统方法,通过编写一个类与enter()和exit()方法,并不困难。但有时比你需要的开销只是管理一个微不足道的上下文。在这类情况下,您可以使用contextmanager() decorat or 生成器函数转换成一个上下文管理器。


    import contextlib

    @contextlib.contextmanager
    def make_context():
      print ' entering'
      try:
        yield {}
       except RuntimeError, err:
        print ' Error:', err
      finally:
        print ' exiting'

    print 'Normal:'

    with make_context() as value:
      print ' inside with statement:', value

    print
    print 'handled ereor:'

    with make_context() as value:
      raise RuntimeError('show example of handling an error')

    print
    print 'unhandled error:'

    with make_context() as value:
      raise ValueError('this exception is not handled')

打印结果


    Normal:
     entering
     inside with statement: {}
      exiting

    handled ereor:
    entering
     Error: show example of handling an error
     exiting

    unhandled error:
    entering
    exiting
    Traceback (most recent call last):
     File "test.py", line 30, in <module>
       raise ValueError('this exception is not handled')
       ValueError: this exception is not handled

嵌套上下文

使用nested()可以同时管理多个上下文。


    import contextlib

    @contextlib.contextmanager
    def make_context(name):
      print 'entering:', name
      yield name
      print 'exiting:', name

    with contextlib.nested(make_context('A'), make_context('B'), make_context('C')) as (A, B,   C):
      print 'inside with statement:', A, B, C

打印结果


    entering: A
    entering: B
    entering: C
    inside with statement: A B C
    exiting: C
    exiting: B
    exiting: A

因为Python 2.7和以后的版本不赞成使用nested(),因为可以直接嵌套


    import contextlib

    @contextlib.contextmanager
    def make_context(name):
      print 'entering:', name
      yield name
      print 'exiting:', name

    with make_context('A') as A, make_context('B') as B, make_context('C') as C:
      print 'inside with statement:', A, B, C

关闭open的句柄

文件类支持上下文管理器, 但是有一些对象不支持。还有一些类使用close()方法但是不支持上下文管理器。我们使用closing()来为他创建一个上下文管理器。(类必须有close方法)


    import contextlib


    class Door(object):
      def __init__(self):
        print ' __init__()'

      def close(self):
        print ' close()'

    print 'Normal Example:'
    with contextlib.closing(Door()) as door:
      print ' inside with statement'

    print 
    print 'Error handling example:'
    try:
      with contextlib.closing(Door()) as door:
        print ' raising from inside with statement'
        raise RuntimeError('error message')
    except Exception, err:
      print ' Had an error:', err

打印结果


    Normal Example:
      __init__()
      inside with statement
      close()

    Error handling example:
      __init__()
      raising from inside with statement
      close()
      Had an error: error message

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8