zoukankan      html  css  js  c++  java
  • with与上下文管理器

    With基本语法

    class Common(object):    
        @classmethod
        def getFileMD5(cls, filepath):
            with open(filepath, 'rb') as f:
                md5obj = hashlib.md5()
                md5obj.update(f.read())
                md5_hash = md5obj.hexdigest()
                return md5_hash

    with语句会在执行完代码块后自动关闭文件。这里无论写文件的操作成功与否,是否有异常抛出,with语句都会保证文件被关闭。

    如果不用with,我们可能要用下面的代码实现类似的功能

    try:
        f = open(filepath, 'rb')
        f.read()
    finally:
        f.close() 

    上面的with代码背后发生了些什么?

    1. 首先执行open( ),返回一个文件对象
    2. 调用这个文件对象的__enter__方法,并将__enter__方法的返回值赋值给变量f
    3. 执行with语句体,即with语句包裹起来的代码块
    4. 不管执行过程中是否发生了异常,执行文件对象的__exit__方法,在__exit__方法中关闭文件。

    这里的关键在于open返回的文件对象实现了__enter__和__exit__方法。一个实现了__enter__和__exit__方法的对象就称之为上下文管理器。

     

    上下文管理器

    上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。__enter__方法在语句体执行之前进入运行时上下文,__exit__在语句体执行完后从运行时上下文退出。

    在实际应用中,__enter__一般用于资源分配,如打开文件、连接数据库、获取线程锁;__exit__一般用于资源释放,如关闭文件、关闭数据库连接、释放线程锁。

     

    自定义上下文管理器

    既然上下文管理器就是实现了__enter__和__exit__方法的对象,我们能不能定义自己的上下文管理器呢?答案是肯定的。

    我们先来看下__enter__和__exit__方法的定义:

    __enter__() - 进入上下文管理器的运行时上下文,在语句体执行前调用。如果有as子句,with语句将该方法的返回值赋值给 as 子句中的 target。

    __exit__(exception_type, exception_value, traceback) - 退出与上下文管理器相关的运行时上下文,返回一个布尔值表示是否对发生的异常进行处理。如果with语句体中没有异常发生,则__exit__的3个参数都为None,即调用 __exit__(None, None, None),并且__exit__的返回值直接被忽略。如果有发生异常,则使用 sys.exc_info 得到的异常信息为参数调用__exit__(exception_type, exception_value, traceback)。出现异常时,如果__exit__(exception_type, exception_value, traceback)返回 False,则会重新抛出异常,让with之外的语句逻辑来处理异常;如果返回 True,则忽略异常,不再对异常进行处理。

     

    理解了__enter__和__exit__方法后,我们来自己定义一个简单的上下文管理器。这里不做实际的资源分配和释放,而用打印语句来表明当前的操作。

    class ContextManager(object):
        def __enter__(self):
            print("[in __enter__] acquiring resources")
    
        def __exit__(self, exception_type, exception_value, traceback):
            print("[in __exit__] releasing resources")
            if exception_type is None:
                print("[in __exit__] Exited without exception")
            else:
                print("[in __exit__] Exited with exception: %s" % exception_value)
                return False
    
    with ContextManager():
        print("[in with-body] Testing")

    运行上面的代码,会得到如下的输出

    [in __enter__] acquiring resources
    [in with-body] Testing
    [in __exit__] releasing resources
    [in __exit__] Exited without exception

     

    我们在with语句体中人为地抛出一个异常

    with ContextManager():
        print("[in with-body] Testing")
        raise(Exception("something wrong"))

    会得到如下的输出

    [in __enter__] acquiring resources
    [in with-body] Testing
    [in __exit__] releasing resources
    [in __exit__] Exited with exception: something wrong
    Traceback (most recent call last):
      File "/tmp/a.py", line 15, in <module>
        raise(Exception("something wrong"))
    Exception: something wrong

    如我们所期待,with语句体中抛出异常,__exit__方法中exception_type不为None,__exit__方法返回False,异常被重新抛出。

    以上,我们通过实现__enter__和__exit__方法来实现了一个自定义的上下文管理器。

     

    contextlib库

    除了上面的方法,我们也可以使用contextlib库来自定义上下文管理器。如果用contextlib来实现,可以用下面的代码来实现类似的上下文管理器

    from contextlib import contextmanager
    
    @contextmanager
    def func():
        try:
            print("[in __enter__] acquiring resources")
            yield
        finally:
            print("[in __exit__] releasing resources")
    
    with func():
        print("[in with-body] Testing")
        raise(Exception("something wrong"))

    上面的代码涉及到装饰器(@contextmanager),生成器(yield),有点难读。这里yield之前的代码相当于__enter__方法,在进入with语句体之前执行,yield之后的代码相当于__exit__方法,在退出with语句体的时候执行。

  • 相关阅读:
    在IIS上启用Gzip压缩(HTTP压缩)
    跨数据库服务器查询和跨表更新
    GOOGLE高级搜索的秘籍
    NET中的规范标准注释(二) -- 创建帮助文档入门篇
    NET中的规范标准注释(一) -- XML注释标签讲解
    如何取出word文档里的图片
    System帐户!我使用你登陆
    横竖两个数字塔的效果BAT批处理怎么写?
    用批处理修改日期,然后在改回来
    根据日期计算星期几----蔡勒(Zeller)公式推导
  • 原文地址:https://www.cnblogs.com/justblue/p/13003513.html
Copyright © 2011-2022 走看看