04.with语句块

本文主题,讲解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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容

  • 转载自:http://mp.weixin.qq.com/s/LO1yyFeUA6pR_YPyfDoSig 姓名:梅...
    虐先森阅读 1,423评论 0 1
  • 引言 with 语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要通过 from...
    氨基钠阅读 349评论 0 2
  • 本文转自浅谈Python的with语句 引言 with 语句是从 Python 2.5 开始引入的一种与异常处理相...
    Syfun阅读 4,008评论 0 50
  • 术语 要使用 with 语句,首先要明白上下文管理器这一概念。有了上下文管理器,with 语句才能工作。下面是一组...
    lmem阅读 297评论 0 0
  • 10种有毒的室内植物: 1、蔓绿绒 蔓绿绒是非常受欢迎的室内盆栽植物,它有着常年翠绿的叶子,有的叶片会裂开,有的叶...
    eff7af6c2f06阅读 196评论 0 1