美文网首页
04.with语句块

04.with语句块

作者: 杨强AT南京 | 来源:发表于2018-10-27 01:09 被阅读22次

本文主题,讲解with的使用与神奇。

  1. with语法与使用前提;
  2. with与异常处理;
  3. 上下文管理器模块contextlib;
  4. 使用with处理图形上下文;

一、with语法与使用前提

1. with语法

语法一

    with  对象:
        with语句

语法二

    with  对象  as  对象别名:
        with语句

2. with使用前提

  先从一段运行错误的代码开始:

#coding=utf-8
a=20
with a:
    print("with body")

该段代码执行错误如下:

with使用的错误情况

该错误提示:对象a的AttributeError:__enter__。

实际上,该错误发生的原因是:

with中使用的对象必须实现__enter__与__exit__函数。

备注:下面的解释引用网上的文章(https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/)。
因为with的工作机制与流程如下:

context_manager = context_expression
exit = type(context_manager).__exit__  
value = type(context_manager).__enter__(context_manager)
exc = True   # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
try:
    try:
        target = value  # 如果使用了 as 子句
        with-body     # 执行 with-body
    except:
        # 执行过程中有异常发生
        exc = False
        # 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
        # 由外层代码对异常进行处理
        if not exit(context_manager, *sys.exc_info()):
            raise
finally:
    # 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
    # 或者忽略异常退出
    if exc:
        exit(context_manager, None, None, None) 
    # 缺省返回 None,None 在布尔上下文中看做是 False

  说明:

  1. 执行 context_expression,生成上下文管理器 context_manager
  2. 调用上下文管理器的 enter() 方法;如果使用了 as 子句,则将 enter() 方法的返回值赋值给 as 子句中的 target(s)
  3. 执行语句体 with-body
  4. 不管是否执行过程中是否发生了异常,执行上下文管理器的 exit() 方法,exit() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 exit(None, None, None) ;如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 exit(exc_type, exc_value, exc_traceback)
  5. 出现异常时,如果 exit(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理
#coding=utf-8
class MyWith:
    def __enter__(self):
        print("enter")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        print(exc_type,exc_val,exc_tb)

o=MyWith()
with o:
    print("执行with过程!")

输出结果如下:

with对象的调用输出

说明:
  1. 没有异常情况下,系统调用__exit__的时候,传递的参数都是None。
  2. 其中context_mamager就是with 后的对象,由__enter__返回,并赋值给as后的变量。
  修改代码,让__enter__返回一个对象,可以看见as后的变量就是__enter__返回对象。

#coding=utf-8
class MyWith:
    def __enter__(self):
        print("enter")
        return "Hello"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        print(exc_type,exc_val,exc_tb)

o=MyWith()
with o as b:
    print("执行with过程!",b)        #b就是__enter__返回的值

执行效果如下:

__enter__的返回值与as后面变量关系

  当with过程发生异常,则会传递异常信息,下面是一段with发生异常情况的代码:

#coding=utf-8
class MyWith:
    def __enter__(self):
        print("enter")
        print(1/0)      #人为制造一个异常
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        print(exc_type,exc_val,exc_tb)

o=MyWith()
with o as b:
    print("执行with过程!",b)        #b就是__enter__返回的值

  由于exit中没有处理异常,所以会转移异常给调用者,下面是运行的异常输出:

没有在exit处理异常的情况

二、with与异常处理

  通过__exit__可以确定是否需要调用者处理异常。返回True,不需要处理,返回False需要处理
  下面是with执行过程产生异常的情路:

#coding=utf-8
import traceback
class MyWith:
    def __enter__(self):
        print("enter")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        print("异常类型",exc_type)
        print("异常值", exc_val)
        print("异常跟踪",exc_tb)
        traceback.print_tb(exc_tb)
        print(":",traceback.format_tb(exc_tb))
        #return False    #需要调用者处理异常
        return True #调用者不处理异常

    def biz(self):
        print(1 / 0)  # 人为制造一个异常

o=MyWith()

with o as b:
    #b.biz()
    print(1 / 0)  # 人为制造一个异常

下面是输出效果,可以观察__exit__传递的异常信息。

异常传递与异常信息

提示:异常的相关处理可以使用traceback模块中的函数。

三、上下文管理器模块contextlib

使用contextlib可以达到一样的效果:

from contextlib import contextmanager
@contextmanager
def TheWith():
    try:
        print('enter')
        yield '返回的as后面的值'
        print("exit")
    except Exception as e:
        print(e)
        print("这里做关闭工作")

with TheWith() as w:
    print("With过程:",w)
    print(1/0)

运行的效果如下:

上下文库的使用效果

这里yield生成器前等于enter,yield生成器后等于exit。
如果有异常,可以在with对象内部处理,不用再with过程中处理。
下面是一段简洁代码可以说明contextlib的执行过程:

@contextmanager
def TheWith():
    print('enter')
    yield '返回的as后面的值'
    print("exit")
with TheWith() as w:
    print("With过程:",w)

运行效果如下:

contextmanager控制下的执行过程

四、with的典型应用

1. 文件操作异常的正常关闭

#coding=utf-8

#传统写法
file = open("with_try.py")
try:
    data = file.read()
    print(data)
finally:
    file.close()

#简洁写法
with open("with_try.py") as file:
    data = file.read()
    print(data)

2. 使用绘图上下文

  这种使用方式在传统的c中使用比较广泛,但是异常发生需要单独的处理机制,在python中使用with得到很好的处理。
  相关的例子代码,可以参考我关于Kivy的系列文章。


资源

本文使用的代码的下载地址: https://github.com/QiangAI/PythonSkill/tree/master/AdvPython/03with

相关文章

  • 04.with语句块

    本文主题,讲解with的使用与神奇。with语法与使用前提;with与异常处理;上下文管理器模块contextli...

  • 第二课小结--循环

    单分支语句if if语句的表达式:if(条件){语句块1;语句块2} 如果我们if语句块只有一条语句,那么可以省略...

  • C#初级(分支语句 循环 (例子))

    1.单分支语句if if语句的表达式:if(条件){语句块1;语句块2}//如果我们if语句块只有一个语句,那么...

  • JS条件分支(学习笔记)

    if 语句 condition:true:执行语句false:跳过 if 语句块,执行 if 语句块后面的语句 i...

  • java 入门 -- java中的语句块

    /* * Java中的语句块: * 语句块:block staetment 使用花括号括起来的语句,构成一个语句块...

  • Python条件语句与循环语句

    条件语句 语法: if 表达式: 语句块 或: if 表达式: 语句块 ...

  • python异常处理

    异常处理: 方式1: try: <语句块1> except: <语句块2> 方 2: try: <语句块1> ex...

  • Bash基础第二节

    if 结构 语句结构: if 代表语句块开始,fi代表语句块结束。 if [condition] : then ....

  • Mr.Li--python循环

    if和while语句 If语句: If条件: 语句块 Else: 语句块 条件必须为真或者是假,如果为真,执行语句...

  • ES6 - 语句块, let 和 const

    概览 语句块(或其他语言中的复合语句)用来组织零个或多条语句。用一对花括号界定语句块。 语句块通常在流程控制语句 ...

网友评论

      本文标题:04.with语句块

      本文链接:https://www.haomeiwen.com/subject/ulqrtqtx.html