with and contextmanager
with
初识with
在Python中,读写文件这样的资源要特别注意,必须在使用完毕后正确关闭它们。正确关闭文件资源的一个方法是使用try...finally
:
try:
f = open("/directory/filename", 'r')
f.read()
finally:
# 如果f不存在, 则应该是文件对象未打开
if f:
f.close()
但是写try:...finally:...
非常繁琐。Python的with
语句允许我们非常方便地使用资源,而不必担心资源没有关闭,所以上面的代码可以简化为:
# 注意f只会在with语句块有效
with open("/directory/filename", 'r') as f:
f.read()
with的工作原理
并不是只有open()
函数返回的fp对象才能使用with
语句。实际上,任何对象,只要正确实现了上下文管理,就可以用于with
语句。
实现上下文管理是通过__enter__
和__exit__
这两个方法实现的. 我们可以简单通过一个例子来说明with
的内部调用方法
class Generator(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print("There's no way out")
return self
def __exit__(self, exc_type, exc_value, traceback):
# 检查退出的类型
# 当有异常发生时 exc_type存在值
if exc_type:
print('Open the door')
else:
print('100%')
def generate(self):
print('%s can uses love to generate electricity' % self.name)
我们可以通过以下代码调用
# 紧跟with后面的语句被求值后,返回对象的 `__enter__() `方法被调用,
# 这个方法的返回值将被赋值给as后面的变量
with Generator("LEX") as lex:
lex.generate()
# 当with后面的代码块全部被执行完之后,将调用返回对象的 `__exit__()`方法。
用爱发电
其实 with
就是这么简单, 大部分初学者只要理解到这里就可以了, 所以小白勿入, 高能级别
上下文管理器
我知道有很多小白还是读了, 那我先介绍几个名词让你们知男而退
-
上下文管理协议(Context Management Protocol):包含方法
__enter__
和__exit__
,支持
该协议的对象要实现这两个方法。
- 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了
__enter__
和__exit__
方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,
负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,
也可以通过直接调用其方法来使用。
-
运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的
__enter__
和
__exit__
方法实现,__enter__
方法在语句体执行之前进入运行时上下文,__exit__
在
语句体执行完后从运行时上下文退出。with
语句支持运行时上下文这一概念。
- 上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式
要返回一个上下文管理器对象。如 例子with
后面的Generator("LEX") as lex:
- 语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管
理器的__enter__
方法,执行完语句体之后会执行 __exit__
方法。
其实这些术语不需要你们立刻理解, 只需要在以后的课程或者项目中慢慢体会.
@contextmanager
编写__enter__
和__exit__
仍然很繁琐,因此Python的标准库contextlib
提供了更简单的写法, 让我们更加简洁的定制自己的上下文管理器. 同样的爱之代码走起:
from context import contextmanager
class Generator(object):
def __init__(self, name):
self.name = name
def generate(self):
print('%s can uses love to generate electricity' % self.name)
@contextamnager
def create_generator(name):
print("I'm generating electricity from love again")
lex = Generator(name)
# 返回给with...as...语句
yield lex
print('100%')
@contextmanager
这个decorator必须接受一个 ==generator== ,用yield
语句把with ... as var
把变量输出出去, 然后属于你的上下文管理器就弄了, 是不是出奇的简单
使用一下这个管理器, 说说感觉
with create_generator("LEX") as lex:
lex.generate()
感觉良好
@closing
但是少年郎们还没结束呢, 让我们了解一下closing
的作用, 它的作用就是让不是上下文管理器的对象转化为上下文管理器, 即可使用with
语句
urllib.request import urlopen
with closing(urlopen('https://www.bilibili.com')) as page:
for line in page:
print(line)
其实closing
内部非常好实现, 内部还是调用了contextmanager
方法
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
closing 不同于 @contextmanager, 他的本身不需要生成器, 可谓加上这行代码, 秒变真男人
总结
-
with
关联着对象的__exit__
和__enter__
方法, 你必须清楚他们是何时被调用, 他们的返回值又返回到哪里去了 -
@contextmanager
所装饰的对象必须是带yield
的生成器,yield
会把后面的值返回给with...as...
语句 -
@closing
原先必许在所装饰的对象定义一个close
方法, 否则会报错, 但是现在不会, 所以这种一行真男人得灵药不多了 -
上下文管理器将会贯穿大型框架结构, 如
flask
的 Application上下文, 请求(request)上下文等等, 学习上下文管理器可以帮助我们更好理解那些框架的工作原理.
未完待续...