在Python中,有些操作可能会出现错误,但是开发者希望出现错误后也能够进行一些必要地收尾操作,比如:文件的关闭、断开数据库连接、相关数据的清理等等。这时,我们可以通过try…except
语句中的finally
子语句进行处理。
其实在Python中,with
语句可以更高效的处理这种事情。with
语句,with语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文。通过上下文管理器,可以实现自动分配并释放资源的功能。
上下文管理器对象用来管理 with语句,这就像迭代器管理 for 语句一样。
with 语句的语法格式如下
with 表达式 [as变量名]:
代码块
上面[]
内的可以省略,如果未省略,则代表将前面的表达式
的结果(即:上下文管理器对象的值)绑定到变量名
中,方面在代码块中调用。
上下文管理器这一功能最常见的用法是在操作文件中。在没有使用上下文管理器时,操作文件的代码如下:
from icecream import ic
file = open('text.txt', mode='r', encoding='utf-8')
try:
ic(file.read())
...
finally:
file.close()
ic| file.read(): ‘i love python!’
from icecream import ic
with open('text.txt', mode='r', encoding='utf-8') as f:
ic(f.read())
...
ic| file.read(): ‘i love python!’
上面的文件操作是上下文管理器最常见的用法,其实,我们可以自己创建一个支持上下文管理器的类,通过该类,我们可以进一步了解上下文管理器的工作原理。
要让类能实现上下文管理器,只需要在创建的类中实现__enter__
和__exit__
两个方法即可。
进入上下文管理器时,调用__enter__
方法,并将该方法的返回值绑定到as后的变量上。一般会直接返回管理器对象,方便后续调用。
不管以何种方式(遇到异常、with语句块代码运行完毕)退出上下文管理器,在退出时都会先调用__exit__
方法。该方法有3个参数,这些参数用于异常处理,参数含义如下:
type:异常类
value:异常值
trace:调用栈信息
上下文管理中,如果出现异常,则可以通过该方法捕获,如果捕获后,该方法返回False,则这个异常还会向上抛出,如果返回True,则不会再向上抛出该异常。
from icecream import ic
import time
class Person:
def __init__(self):
time.sleep(1)
ic('__init__ 方法被调用啦!')
def __enter__(self):
time.sleep(1)
ic("__enter__ 方法被调用啦!")
return self
def __exit__(self, err_of_type, err_of_value, err_of_trace):
time.sleep(1)
ic("__exit__ 方法被调用啦!")
ic(err_of_type, err_of_value, err_of_trace)
if err_of_type is ZeroDivisionError:
ic("Zero value Error!")
elif issubclass(err_of_type, Exception):
pass
return True
p = Person()
ic(type(p), p)
with Person() as person:
time.sleep(1)
ic(type(person), person)
raise ZeroDivisionError("zero error!")
ic('done')
ic('end')
16:59:39|> t1.py:8 in init()- ‘init 方法被调用啦!’
16:59:39|> t1.py:27 in
type§:main.Person’>
p: <main.Person object at 0x000001C2FA2E0790>
16:59:40|> t1.py:8 in init()- ‘init 方法被调用啦!’
16:59:41|> t1.py:12 in enter()- ‘enter 方法被调用啦!’
16:59:42|> t1.py:30 in
type(person):main.Person’>
person: <main.Person object at 0x000001C2FB1E2C40>
16:59:43|> t1.py:17 in exit()- ‘exit 方法被调用啦!’
16:59:43|> t1.py:18 in exit()
err_of_type:
err_of_value: ZeroDivisionError(‘zero error!’)
err_of_trace:
16:59:43|> t1.py:20 in exit()- ‘Zero value Error!’
16:59:43|> t1.py:33 in - ‘end’
在with语句块中,如果有异常,那么会先调用上下文管理器中的__exit__
方法,在这个方法中我们可以捕获异常并处理,但是,with语句块后续的代码则不会被运行了,也就是结束了整个with语句。所以,with语句块在可能出现异常的后续代码可以写在__exit__
方法中捕获相应的异常之后。
在Python标准库中,contextlib这个库可以提供一些上下文管理器相关工具。
如果对象提供了 close() 方法,但没有实现__enter__
、__exit__
方法,那么可以使用这个函数构建上下文管理器。
from icecream import ic
from contextlib import closing
import time
class Person:
def __init__(self):
time.sleep(1)
ic('__init__ 方法被调用啦!')
def close(self):
time.sleep(1)
ic("__exit__ 方法被调用啦!")
return True
with closing(Person()) as person:
time.sleep(1)
ic(type(person), person)
raise ZeroDivisionError("zero error!")
ic('done')
ic('end')
17:08:31|> test.py:9 in init()- ‘init 方法被调用啦!’
17:08:32|> test.py:19 in
type(person):main.Person’>
person: <main.Person object at 0x0000025D0C9C0790>
17:08:33|> test.py:13 in close()- ‘exit 方法被调用啦!’
Traceback (most recent call last):
File “E:/BaiduNetdiskWorkspace/FrbPythonFiles/test.py”, line 20, in
raise ZeroDivisionError(“zero error!”)
ZeroDivisionError: zero error!
对比正常的上下文管理器,其跳过了__enter__
方法,在退出时调用了close
方法。
这个装饰器可以把简单的生成器函数装饰成上下文管理器,这样就不用单独去创建上下文管理器类了。
from icecream import ic
from contextlib import contextmanager
import time
@contextmanager
def myfun():
ic("begin")
yield 1
ic("end")
with myfun() as mf:
time.sleep(1)
ic(type(mf), mf)
ic('done')
ic('ending')
17:21:39|> test.py:8 in myfun()- ‘begin’
17:21:41|> test.py:15 in - type(mf):, mf: 1
17:21:41|> test.py:16 in - ‘done’
17:21:41|> test.py:10 in myfun()- ‘end’
17:21:41|> test.py:17 in - ‘ending’
ContextDecorator用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。
from icecream import ic
from contextlib import ContextDecorator
import time
class Person(ContextDecorator):
def __enter__(self):
ic('begin')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
ic('exit')
return True
@Person()
def myfun():
ic('myfun')
myfun()
17:29:28|> test.py:8 in enter()- ‘begin’
17:29:29|> test.py:18 in myfun()- ‘myfun’
17:29:29|> test.py:12 in exit()- ‘exit’
Python1.10已支持使用外层圆括号来使多个上下文管理器可以连续多行地书写。 这允许将过长的上下文管理器集能够以与之前 import 语句类似的方式格式化为多行的形式。 例如,以下这些示例写法现在都是有效的:
with (CtxManager() as example):
pass
with (
CtxManager1(),
CtxManager2()
):
pass
with (CtxManager1() as example,
CtxManager2()):
pass
with (CtxManager1(),
CtxManager2() as example):
pass
with (
CtxManager1() as example1,
CtxManager2() as example2
):
pass
在被包含的分组末尾过可以使用一个逗号作为结束:
with (
CtxManager1() as example1,
CtxManager2() as example2,
CtxManager3() as example3,
):
pass