前言
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等。
问题引出
如下代码:
上面代码存在2个问题:
(1)文件读取发生异常,但没有进行任何处理;
(2)可能忘记关闭文件句柄;
改进
虽然这段代码运行良好,但比较冗长。
而使用with的话,能够减少冗长,还能自动处理上下文环境产生的异常。如下面代码:
with 工作原理
(1)紧跟with后面的语句被求值后,返回对象的 –enter–() 方法被调用,这个方法的返回值将被赋值给as后面的变量;
(2)当with后面的代码块全部被执行完之后,将调用前面返回对象的 –exit–() 方法。
with工作原理代码示例:
运行结果:
整个运行过程如下:
(1)enter()方法被执行;
(2)enter()方法的返回值,在这个例子中是”Foo”,赋值给变量sample;
(3)执行代码块,打印sample变量的值为”Foo”;
(4)exit()方法被调用;
【注】exit()方法中有3个参数, exc_type, exc_val, exc_tb,这些参数在异常处理中相当有用。
exc_type: 错误的类型
exc_val: 错误类型对应的值
exc_tb: 代码中错误发生的位置
示例代码:
输出结果:
总结
实际上,在with后面的代码块抛出异常时,exit()方法被执行。开发库时,清理资源,关闭文件等操作,都可以放在exit()方法中。
总之,with-as表达式极大的简化了每次写finally的工作,这对代码的优雅性是有极大帮助的。
如果有多项,可以这样写:
<code>with</code>语句的原理
上下文管理协议(Context Management Protocol):包含方法 __enter__() 和 __exit__() ,支持该协议的对象要实现这两个方法。
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了 __enter__() 和 __exit__() 方法。上下文管理器定义执行<code>with</code>语句时要建立的运行时上下文,负责执行<code>with</code>语句块上下文中的进入与退出操作。通常使用<code>with</code>语句调用上下文管理器,也可以通过直接调用其方法来使用。
<code>with</code>语句的常用表达式:
其一般的执行过程是这样的:
1、执行EXPR,生成上下文管理器context_manager;
2、获取上下文管理器的 __exit()__ 方法,并保存起来用于之后的调用;
3、调用上下文管理器的 __enter__() 方法;如果使用了<code>as</code>子句,则将 __enter__() 方法的返回值赋值给<code>as</code>子句中的VAR;
4、执行BLOCK中的表达式;
5、不管是否执行过程中是否发生了异常,执行上下文管理器的 __exit__() 方法, __exit__() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句<code>break/continue/return</code>,则以<code>None</code>作为参数调用<code>__exit__(None, None, None)</code>;如果执行过程中出现异常,则使用<code>sys.exc_info</code>得到的异常信息为参数调用<code>__exit__(exc_type, exc_value, exc_traceback)</code>;
6、出现异常时,如果<code>__exit__(type, value, traceback)</code>返回False,则会重新抛出异常,让<code>with</code>之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。
自定义上下文管理器
<code> Python</code>的<code>with</code>语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。
示例1:
【注】with后面必须跟一个上下文管理器,如果使用了as,则是把上下文管理器的 __enter__() 方法的返回值赋值给 target,target 可以是单个变量,或者由“()”括起来的元组(不能是仅仅由“,”分隔的变量列表,必须加“()”)
代码运行结果如下:
结果分析:当我们使用with的时候,__enter__方法被调用,并且将返回值赋值给as后面的变量,并且在退出with的时候自动执行__exit__方法。
示例2:
去期待陌生,去拥抱惊喜。