with与上下文管理器

with open('output.txt','w') as f:
    f.write('Hello World')

上面的代码往output文件写入了Hello world字符串,with语句会在执行完代码块后自动关闭文件。这里无论写文件的操作成功与否,是否有异常抛出,with语句都会保证文件被关闭。

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

try:
    f = open('output.txt','w')
    f.write("Hello World")
except Exception as e:
    pass
finally:
    f.close()

可以看到使用了with的代码比上面的代码简洁许多。

上面的with代码背后发生了些什么?我们来看下它的执行流程

首先执行open(‘output’, ‘w’),返回一个文件对象
调用这个文件对象的enter方法,并将enter方法的返回值赋值给变量f
执行with语句体,即with语句包裹起来的代码块
不管执行过程中是否发生了异常,执行文件对象的exit方法,在exit方法中关闭文件。
这里的关键在于open返回的文件对象实现了enterexit方法。一个实现了_enter__exit_方法的对象就称之为上下文管理器。

上下文管理器

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

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

自定义上下文管理器

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

我们先来看下enterexit方法的定义:

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,则忽略异常,不再对异常进行处理。

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

class ContextManager(object):
    def __enter__(self):
        print('[in __enter__] acquiring responses')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('[in __exit__] releasing resources')
        if exc_type is None:
            print('[in __exit__] Exited without exception')
        else:
            print('[in __exit__] Exited with exception:%s' %exc_val)
            return False
with ContextManager():
    print('[in with-body] Testing')

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

[in __enter__] acquiring responses
[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 responses
[in with-body] Testing
[in __exit__] releasing resources
[in __exit__] Exited with exception:something wrong
Traceback (most recent call last):
  File "/home/python/Desktop/demo2/cc/with/demo001.py", line 27, in <module>
    raise(Exception('something wrong'))
Exception: something wrong

Process finished with exit code 1

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

而返回true ,异常不被抛出:
这里写图片描述

运行结果:

[in __enter__] acquiring responses
[in with-body] Testing
[in __exit__] releasing resources
[in __exit__] Exited with exception:something wrong

Process finished with exit code 0

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

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语句体的时候执行。

猜你喜欢

转载自blog.csdn.net/Enjolras_fuu/article/details/80501891
今日推荐