Python上下文管理器

今天在地铁上又看了一遍Python的上下文管理器,自己好像也没有完整记录过,所以刚到单位,就想写一篇文章。

本文主要讲解:

  1. Python上下文管理的定义与作用;
  2. 上下文管理器的使用;
  3. 自定义上下文管理器;
  4. 上下文管理器装饰器

 

一、Python上下文管理的定义与作用

        上下文管理器是Python2.5之后才出现的概念。上下文管理器规定了某个对象的使用范围,当进入或者离开了使用范围,都会有相应的一些调用,比如代码块开始时执行一些准备,代码块结束时结束一些操作。它更多的是用于资源的分配和释放上,即在开始时分配资源,结束时释放一些资源。比如在执行数据库查询时要建立连接,查询结束后要释放连接;写文件时要先打开文件,写结束后,要关闭文件等等。还有,就是资源的加锁和解锁,比如在使用多线程时,可能会用到加锁和解锁。

        上下文管理器可以通过使用更可读、更精简的代码实现资源的分配与释放。

二、上下文管理器的使用

        上下文管理器的一个常见例子,就是建立或关闭数据库连接。使用装饰器实现:

from  baseclass.database import Database

def dbdecorator(func):
   def wrapper(*args,**kwargs):
     db = Database()
     db.connect()
     ret = func(db,*args,**kwargs)
     db.close()
     return ret
   return wrapper()


@dbdecorator
def query(db,*args,**kwargs):
   return db.query(*args,**kwargs)




        对于上下文管理器的使用,最常见的是使用with语句,with语句可构建资源的分配与释放的语法糖。先拿最常见的例子来说,即文件的打开与关闭。

正常语法:

def load_data(filename):
   f = file(filename,'w')
   try:
      f.write('test file')
   finally:
      f.close()

即正常情况下,你要显示的打开和关闭文件。但如果你用with语句,就会更可读,且永远不会因为忘记关闭文件而担忧:

def load_data(filename):
   with file(filename,'w')  as f:
      f.write('test file')
   

 

三、自定义上下文管理器

        要实现一个自定义的上下文管理器,肯定要实现两个方法,一是进入对象范围时的准备工作,二是离开对象范围时的结束工作。

        Python提供了两个类的方法分别实现上述功能:

  •     __enter__  进入对象范围时(一般代码块开始)被调用;
  •     __exit__  离开对象范围时(代码块结束)呗调用;

        因此,一个Python类,只要实现了上述两种方法,就可以说是一个上下文管理器。

class  FileRead(object):

    def __init__(self,filename,read_method='w'):
       self.obj = file(filename,read_method)

    def __enter__():
       return self.obj

    def __exit__():
       self.obj.close()



with FileRead('test.txt') as f:
    f.write('s')

再拿一个例子看看,Flask源码:

class IdentityContext(object):
    """The context of an identity for a permission.
    The principal behaves as either a context manager or a decorator. The
    permission is checked for provision in the identity, and if available the
    flow is continued (context manager) or the function is executed (decorator)
    """
    
    
    def __init__(self, permission, http_exception=None):
        self.permission = permission
        self.http_exception = http_exception
    
    @property
    def identity(self):
        """The identity of this principal
        """
        return g.identity
    
    def can(self):
        """Whether the identity has access to the permission
        """
        return self.identity.can(self.permission)
    
    def __call__(self, f):
        @wraps(f)
        def _decorated(*args, **kw):
            with self:
                rv = f(*args, **kw)
            return rv
        return _decorated
    
    def __enter__(self):
        # check the permission here
        if not self.can():
            if self.http_exception:
                abort(self.http_exception, self.permission)
            raise PermissionDenied(self.permission)
    
    def __exit__(self, *args):
        return False

 

        上面的IdentityContext是一个类装饰器,实现了上下文管理器的功能。该类定义了__enter__和__exit__两个方法。

class  Permission(object):
    def require(self, http_exception=None):
    """Create a principal for this permission.


       return IdentityContext(self, http_exception)

        上面就实现了上下文管理器的功能,那为什么这里没使用with语句,就可以呢?

        如果细心的话,可能会看到IdentityContext的__call__方法,它里面使用了with语法。

         with self:语句开始就自动执行__enter__ 方法,with语句结束,就执行__exit__ 方法。

          那这个__call__方法的作用是啥?

        对象通过提供__call__方法可以模拟函数的行为,如果一个对象提供了该方法,就可以像函数一样使用它,也就是说x(arg1, arg2...) 等同于调用x.__call__(self, arg1, arg2) 。比如:

      

class  Test(object):
   def __call__(a):
      print a



t = Test()
t('e')

>> e

说完了__call__,再看内部语句,实际上是一个函数装饰器,并且调用自己。

 

对于异常的处理,如果发现异常,就会调用__exit__方法,如果我们想忽略异常,就直接让其返回True即可。

 

def __exit__(self,exc_type, exc_val, exc_t):

   #三个参数exc_type, exc_val, exc_tb分别代表异常类型,异常值,和异常的Traceback
   return True

四、上下文管理器装饰器

        除了上面要写__enter__和__exit方法,我们可以用标准库中的装饰器改写,即contextlib.contextmanager。

        

@contextmanager
def test_context():
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        print 'it cost {0}'.format(end-start)

with test_context():
    time.sleep(10)


>>it cost 10.0009999275

 

test_context函数中yield之前的代码类似__enter__方法;yield之后的代码类似于__exit__方法。

--------EOF---------
微信分享/微信扫码阅读