with/as

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

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

用法:

  1. with expression [as var]:
  2. with_block_code

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

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

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

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

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

它实际上等价于:

  1. myfile = open(r'd:\a\b\c\a.log')
  2. try:
  3. for line in myfile:
  4. print(line)
  5. ...more code here...
  6. finally:
  7. myfile.close()

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

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

嵌套多个环境管理器

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

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

它等价于嵌套的with:

  1. with A() as a:
  2. with B() as b:
  3. ...statements...

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

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

  1. with open('a.file') as f1, open('b.file') as f2:
  2. for pair in zi[(f1, f2):
  3. 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值

看一个简单的示例:

  1. class TraceBlock:
  2. def message(self, arg):
  3. print('running ' + arg)
  4. def __enter__(self):
  5. print('starting with block')
  6. return self
  7. def __exit__(self, exc_type, exc_value, exc_tb):
  8. if exc_type is None:
  9. print('exited normally\n')
  10. else:
  11. print('raise an exception! ' + str(exc_type))
  12. return False

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

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

测试下:

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

结果如下:

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

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

contextlib模块

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

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

  1. from contextlib import contextmanager
  2. @contextmanager
  3. def managed_resource(*args, **kwds):
  4. # Code to acquire resource, e.g.:
  5. resource = acquire_resource(*args, **kwds)
  6. try:
  7. yield resource
  8. finally:
  9. # Code to release resource, e.g.:
  10. release_resource(resource)
  11. >>> with managed_resource(timeout=3600) as resource:
  12. ... # Resource is released at the end of this block,
  13. ... # 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. Oracle创建物化视图

    1.物化视图语法 create materialized view [view_name] refresh [fast|complete|force] [ on [commit|demand] | s ...

  2. 解决ssh连接慢,挂起在SSH2_MSG_SERVICE_ACCEPT信息之后很久的问题

    问题现象:无论是ssh 还是 scp 都会连接很久才提示输认证信息(注意:是连接慢) 如本机IP:192.168.18.208 (Hostname: ThinkPad) 目标主机:192.168.18 ...

  3. 20145232韩文浩《网络对抗》PC平台逆向破解

    shellcode注入 1.Linux下有两种基本构造攻击buf的方法:retaddr+nop+shellcode,nop+shellcode+retaddr.我们采用anything+retaddr ...

  4. Linux(Ubuntu-CentOS)

    2017.3.29 查看已安装软件版本 dpkg-query --list 2017.3.3 Ubuntu 14.04 安装 phpmyadmin make sure apache works wel ...

  5. P层

    package net.goeasyway.uploadimage.presenter; import net.goeasyway.uploadimage.model.Photo;import net ...

  6. vue图片上传

    之前花了两个月用vue做了一个建筑照片我的webApp,前端就我一人,负责用vue写页面对接接口,后台一个程序员负责写接口,嵌套个安卓ios的壳.搞的是风风火火,过程也是很心累,大多不在技术,在于所谓 ...

  7. 关于使用jquery.cookie.js存cookie中文出现乱码问题

    一.在Web开发中,有事为了页面之间传值,我们会用到cookie.但是当在cookie中存值为中文汉字时就会出现乱码! 这是一个简单例子: <!DOCTYPE html><html ...

  8. js 原型链解密

    1.对象继承 javaScript的继承不是通过class实现的,而是通过“原型对象”(prototype). 1.每一个函数都有一个原型对象 2.每一个实例对象都有一个_proto_属性 2.原型链 ...

  9. opencl 参考源码及benchmark

    转载:https://www.zhihu.com/question/25539755/answer/44917891 CUDA 5之前的版本有OpenCL的sample,可以上网找找看 AMD APP ...

  10. 12 week work

    调用一个地图API <html> <head> <meta http-equiv="Content-Type" content="text/ ...