python黑魔法——上下文管理器

一个熟悉的操作:

下面的操作是我们在文件处理中较为常用的一种方法,使用这种方法读写文件的时候,当with语句下的处理代码块执行完毕后会自动关闭文件读写流,而不需要f.close()

with open('/path/to/file', 'r') as f:
    # <处理代码块开始>
    print(f.read())
    ...
    # <处理代码块结束>
...

这就是上下文管理器给我的第一印象,其实它的作用不止限于文件读写操作

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

实现自己的上下文管理器

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

import time

class Getime(object):

    def __init__(self, Is_print=False):
        self.Isprint = Is_print

    def __enter__(self):
        self.pre_time = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cur_time = time.time()
        delta_time = self.cur_time - self.pre_time
        if self.Isprint:
            print("the delta time is {0}".format(delta_time) + "s")


# 调用
>>> with Getime(True):
      for i in range(10000000):
          pass

>>> the delta time is 0.4488217830657959s

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

  • __enter__ 进入对象范围时(一般代码块开始)被调用。配合with语句使用的时候,上下文管理器会自动调用__enter__方法,然后进入运行时上下文环境,如果有as 从句,返回自身或另一个与运行时上下文相关的对象,值赋值给as后跟的变量

  • __exit__ 离开对象范围时(代码块结束)被调用。当with块执行完毕退出with语句块或者with代码块出现异常,则会自动执行__exit__方法,并且会把对于的异常参数传递进来。如果__exit__函数返回True。则with语句代码块不会显示的抛出异常,终止程序,如果返回None或者False,异常会被主动raise,并终止程序。exc_type, exc_val, exc_tb 三个参数指出错误信息
    (更多关于__exit__异常处理的细节问题可以参考http://python.jobbole.com/82289/

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


梳理一下便是:
  1. 执行 contextor 以获取上下文管理器
  2. 加载上下文管理器的 exit() 方法以备稍后调用
  3. 调用上下文管理器的 enter() 方法
  4. 如果有 as var 从句,则将 enter() 方法的返回值赋给 var
  5. 执行子代码块 with_body
  6. 调用上下文管理器的 exit() 方法,如果 with_body 的退出是由异常引发的,那么该异常的 type、value 和 traceback 会作为参数传给 exit(),否则传三个 None
  7. 如果 with_body 的退出由异常引发,并且 exit() 的返回值等于 False,那么这个异常将被重9新引发一次;如果 exit() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码

所以在代码或函数执行的时候,调用函数时候有一个环境,在不同的环境调用,有时候效果就不一样,这些不同的环境就是上下文。例如数据库连接之后创建了一个数据库交互的上下文,进入这个上下文,就能使用连接进行查询,执行完毕关闭连接退出交互环境。创建连接和释放连接都需要有一个共同的调用环境。不同的上下文,通常见于异步的代码中。


查看当前类是否支持上下文管理器

例如我们查看文件读写类open

>>> print(dir(open))

['__call__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__self__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__text_signature__']

可以看到, 列表中存在__enter__函数和__exit__函数,故open支持上下文管理器方法


方案二:
通过python提供的上下文管理工具contextlib可以更优雅的实现上下文管理器,具体参见http://python.jobbole.com/87317/

猜你喜欢

转载自blog.csdn.net/MSDN_tang/article/details/80544494