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)上下文等等, 学习上下文管理器可以帮助我们更好理解那些框架的工作原理.
未完待续...
引用
廖雪峰 contextlib