with/as

使用open打开过文件的对with/as都已经非常熟悉,其实with/as是对try/finally的一种替代方案。

当某个对象支持一种称为"环境管理协议"的协议时,就会通过环境管理器来自动执行某些善后清理工作,就像finally一样:不管中途是否发生异常,最终都会执行某些清理操作。

用法:

with expression [as var]:
with_block_code

当expression返回的对象是支持环境管理协议的时候,就可以使用with。as var是可选的,如果不使用as var,expression返回对象将被丢弃,如果使用as var,就会将expression的返回对象赋值给变量var。

整个流程大致如下:先评估expression,如果支持环境管理协议,然后开始with/as语句块结构,当准备退出with语句块的时候,将执行对象中定义的善后操作。工作机制的细节见下文。

例如,open()返回的文件对象是支持环境管理协议的,所以可以用with/as来安全地打开文件:

with open(r'd:\a\b\c\a.log') as logfile:
for line in logfile:
print(line)
...more code here...

整个过程是先open(),然后with/as,输出每一行后将要退出with语句块的时候,环境管理器根据文件对象中定义的操作关闭文件。

它实际上等价于:

myfile = open(r'd:\a\b\c\a.log')
try:
for line in myfile:
print(line)
...more code here...
finally:
myfile.close()

虽然在文件不被引用之后,垃圾回收器会自动回收这个文件对象,但是垃圾回收器的回收操作是有等待时间的。换句话说,如果不使用with/as打开文件,也不显示close()关闭文件,那么这个文件很可能会在用完之后保持空闲一段时间,然后才被垃圾回收器回收。

with/as不仅用于文件打开/关闭,锁操作也支持环境管理协议,也就是说,在有需要的时候会自动释放锁资源。

嵌套多个环境管理器

在python 3.1之后,with as支持多个环境管理器,使用逗号隔开即可。

with A() as a, B() as b:
...statements...

它等价于嵌套的with:

with A() as a:
with B() as b:
...statements...

多环境管理器管理的多个对象会在with语句块中出现异常的时候,或者执行完with语句块的时候全部自动被清理(例如文件关闭操作)。

例如,打开两个文件,将它们的内容通过zip()合并在一起,并且同时关闭它们:

with open('a.file') as f1, open('b.file') as f2:
for pair in zi[(f1, f2):
print(pair)

自定义环境管理器

无论是文件还是锁,都是别人已经写好了环境管理器的对象。我们自己也可以写环境管理器,让它可以使用with/as,这实际上属于运算符重载的范畴。

要写自己的环境管理器,先了解with/as的工作机制的细节:

  1. 先评估expression,评估的返回结果是一个对象,这个对象要具有__enter____exit__方法,返回的对象称为"环境管理器"
  2. 然后调用环境管理器的__enter__方法。__enter__方法的返回值赋值给 as 指定的变量,或者直接丢弃(没有使用as)
  3. 然后执行with语句块中的内容
  4. 如果执行with语句块中的内容时抛出了异常,将调用__exit__(type,value,traceback)方法,其中这3个和异常相关的参数来源于sys.exc_info。如果__exit__返回值为False,则会自动重新抛异常以便传播异常,否则异常被认为合理处理
  5. 如果with语句块中的内容没有抛异常,则直接调用__exit__(None,None,None),即这三个参数都传递为None值

看一个简单的示例:

class TraceBlock:
def message(self, arg):
print('running ' + arg) def __enter__(self):
print('starting with block')
return self def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is None:
print('exited normally\n')
else:
print('raise an exception! ' + str(exc_type))
return False

上面的__enter__方法返回的对象会赋值给as关键字指定的变量,在这个示例中即将对象自身返回。如果有需求,可以返回其它对象。

上面的__exit__中,如果异常的类型为None,说明with语句块中的语句执行过程没有抛异常,正常结束即可。但是如果有异常,则要求返回False,实际上上面的return False可以去掉,因为函数没有return时默认返回None,它的布尔值代表的就时False。

测试下:

with TraceBlock() as action:
action.message("test 1")
print("reached") print('-' * 20, "\n") with TraceBlock() as action:
action.message("test 2")
raise TypeError
print("not reached")

结果如下:

starting with block
running test 1
reached
exited normally -------------------- starting with block
running test 2
raise an exception! <class 'TypeError'>
Traceback (most recent call last):
File "g:/pycode/list.py", line 23, in <module>
raise TypeError
TypeError

定义环境管理器不是件简单的事。一般来说,如果不是很复杂的需求,直接使用try/finally来定义相关操作即可。

contextlib模块

在自定义上下文管理器的时候,可以通过contextlib模块的contextmanager装饰器来简化,这样不需要自己去写__enter____exit__

使用contextlib.contextmanager装饰的时候,所装饰的对象必须是一个生成器函数,且该生成器函数必须只yield一个值,这个值将会被绑定到with/as的as子句的变量上。

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
# Code to acquire resource, e.g.:
resource = acquire_resource(*args, **kwds)
try:
yield resource
finally:
# Code to release resource, e.g.:
release_resource(resource) >>> with managed_resource(timeout=3600) as resource:
... # Resource is released at the end of this block,
... # even if code in the block raises an exception

它的执行流程是这样的:

  1. 当执行到with语句的时候,首先评估managed_resource(timeout=3600),它是一个生成器函数,它会返回一个生成器
  2. 然后执行resource = acquire_resource,并在yield的地方状态被挂起这个生成器函数
  3. 在yield挂起的时候,with语句块中的语句开始执行
  4. 当with语句块退出的时候,yield被恢复,于是继续执行生成器函数中后面的语句

如果在with语句块中发生了异常且未处理,则会在生成器yield被恢复的时候再次抛出这个异常。因此,可以使用try...except...finally语句来捕获可能存在的错误,以便保证yield后面的语句(通常是资源释放类的善后工作)可以正常执行。

Python with/as和contextlib上下文管理使用说明的更多相关文章

  1. python contextlib 上下文管理器

    1.with操作符 在python中读写文件,可能需要这样的代码 try-finally读写文件 file_text = None try: file_text = open('./text', 'r ...

  2. contextlib 上下文管理器

    在Python中,读写文件这样的资源要特别注意,必须在使用完毕后正确关闭它们.正确关闭文件资源的一个方法是使用try...finally: try: f = open('/path/to/file', ...

  3. 流畅的python第十五章上下文管理器和else块学习记录

    with 语句和上下文管理器for.while 和 try 语句的 else 子句 with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文.这么做能避免错误并减少样板代码, ...

  4. python实现可以被with上下文管理的类或函数

      # .开始之前先明确一下with机制 # 1.类包函数__enter__()和__exit__()函数,即是可以被上下文管理的类 # __enter__用来执行with时的方法,__exit__返 ...

  5. python框架之Flask(4)-上下文管理

    知识储备 偏函数 作用 偏函数,帮助开发者自动传递参数. 使用 import functools def index(a1, a2): return a1 + a2 # 原来的调用方式 # ret = ...

  6. (转)contextlib — 上下文管理器工具

    原文:https://pythoncaff.com/docs/pymotw/contextlib-context-manager-tool/95 这是一篇社区协同翻译的文章,你可以点击右边区块信息里的 ...

  7. Python全栈day28(上下文管理)

    我们知道在操作文件对象的时候可以这么写 with open('a.txt',''r) as f: 代码 上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明 ...

  8. python中的with与上下文管理器

    #转载请留言联系 很多人平时需要打开文件进行读取写入操作时,通常这样: f = open('文件路径','w') f.write(data) f.close 这样写有一个潜在的问题,如果在调用 wri ...

  9. python深浅拷贝&垃圾回收&上下文管理(with语句)

    深浅拷贝 在Python中使用copy模块用于对象的拷贝操作. 该模块提供了两个主要的方法:浅拷贝 copy.copy() 深拷贝 copy.deepcopy() 1.浅拷贝(copy) 浅拷贝: 不 ...

随机推荐

  1. vue el-upload form 同时提交

    项目需要form 表单和文件上传同时在一个请求,废话不多说上代码: 上传的组件使用pug格式 .row.my-4 .col-12 el-form(:model='domain', :rules='va ...

  2. noip第29课资料

  3. 关于NGUI Shader 和 Draw Call的优化 & 模糊shader

    序: 1.项目过程中不可避免的需要用到大量Shader 和 UITexture,由于Ngui对Shader支持非常糟糕,导致项目drawCall异常的高 2.Panel裁剪无法裁剪自定义shader内 ...

  4. ubuntu下file_get_contents返回空字符串

    ubuntu下file_get_contents返回空字符串 | 浏览:302 | 更新:2014-03-30 10:11 本文起初面临的问题是PHP中SoapClient不好使,最后file_get ...

  5. JSP Ueditor 实现图片跨域上传

    Ueditor的单图上传,在官方文档上明确写了不支持 然后通过百度找了许多方案,终于有一个可以解决了. http://www.cnblogs.com/hpnet/p/6290452.html 不过那个 ...

  6. Android 视频播放器 (三):使用NBPlayer播放直播视频

    一.前言 在 Android 音视频开发学习思路 中,我们不断的学习和了解音视频相关的知识,随着知识点不断的学习,我们现在应该做的事情,就是将知识点不断的串联起来.这样才能得到更深层次的领悟.通过整理 ...

  7. Alibaba FastJson 常用方法使用指南

    介绍 FastJson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean. FastJso ...

  8. 一、activiti工作流(workflow)入门介绍

    activiti官方网站(官网通常很卡,不建议看,直接看我教程就行) http://www.activiti.org/ eclipse离线安装activiti插件并下载教程 https://downl ...

  9. MySQL 非空约束位置不同对自增列造成的影响

    MySQL版本 select version(); +------------+ | version() | +------------+ | 5.7.21-log | +------------+ ...

  10. Ubuntu双网卡设置内外网上网的问题

    UBUNTU16.04系统,双网卡:eth0, eth1.分贝设置成Public IP, 和局域网IP, 这样这台计算机就可以访问局域网内的各个IP, 同时还可以在全球各地被访问,使用ssh or r ...