Python:上下文管理器 与 contextlib 的用法解析

1. 上下文管理器

1.1. 引言

  • 在 python 语言中经常会使用 with ... as ... 语句,常见的 with open 语句就是其典型代表,如下:
with open('filename.csv') as file:
    print(file.readlines())
  • 我们之所以会使用上面的代码打开并操作文件,是因为它是一个上下文管理器、实现了上下文管理;
  • 那么上下文管理器到底是个什么东西,为什么要使用上下文管理器,接下来的内容将对此做出解答;

1.2. 什么是上下文管理器

  • 定义:
    -- 在一个类里,如果同时实现了__ enter __ 和 __ exit __ 的方法,这个类的实例就是一个上下文管理器,它可以使用 with 语句创建一个上下文管理器;
  • 示例:

>>> class Sample():
...     def __enter__(self):
...         print("get resource")                    # 打印 获取资源
...         return self                              # 注意:此处一定要返回 self 实例
...     def __exit__(self, exc_type, exc_val, exc_tb):
...         print("release resource")                # 打印释放资源
...     def do_job(self):
...         print("do job")
...
>>>
>>> with Sample() as sample:
...     sample.do_job()
...
get resource        # 获取资源
do job              # 执行任务
release resource    # 释放资源
>>>

-- 在上面的代码语句中:

with Sample() as sample: 是 上下文表达式
Sample() 是 上下文管理器
sample 是 资源对象

1.3. __ enter __ 的作用是什么

  • __ enter __ 用于 赋值 给 as 后面的变量;
  • 不过 with 语句中 as 不是必须的;

1.4. __ exit __ 的作用是什么

-- __ exit __ 用于 捕获异常,它的返回值是一个 boolean 对象;
-- 除了 self 之外,必须传入另外三个参数,分别表示 exception 的类型,值,以及 traceback;
-- 如果返回 True 则表示这个异常被忽略;
-- 如果返回 None,False 等则这个异常会抛出。

1.5. 上下文管理器的两个作用

  • 上下文管理器 两大作用:
    -- 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接;
    -- 可以以一种更加优雅的方式,处理异常
  • 作用一:操作资源

-- 还是以第一段代码为例:

with open('filename.csv') as file:
    print(file.readlines())

-- 操作文件流程:

1. open('filename.csv') 创建上下文管理器 > 2. 获取资源 file > 3. readlines() 阅读行 > 4. 释放资源 file

  • 作用二:处理异常
    -- 处理异常,主要是 __ exit __ 方法的,若在执行流程中因为错误而退出,调用 __ exit__ 时,会自动捕获错误信息;
    -- 如果 __ exit__ 最后返回 True,则表示异常被忽略,即使出现异常程序也会继续执行下去,而不会抛出异常中断程序,这将增加代码的健壮性;
>>> class Sample():
...     def __enter__(self):
...         return self
...     def __exit__(self, exc_type, exc_value, traceback):
...         """
...         若在执行流程中因为错误而退出,调用 exit 时,会自动捕获错误信息
...         exc_type: 错误的类型(异常类型)
...         exc_val: 错误类型对应的值 (异常值)
...         exc_tb: 代码中错误发生的位置 (错误栈)
...         """
...         if exc_type == IndexError:
...             print("执行出错 IndexError")
...             print(exc_value, type(exc_value))
...             print(traceback)
...             return True
...         elif exc_type == ZeroDivisionError:
...             print("执行出错 ZeroDivisionError")
...             print(exc_value, type(exc_value))
...             print(traceback)
...             return True
...     def do_wrong_job(self):
...         a = 1 / 0                       # 1 / 0 ,是一个错误的式子,应该会报错
...         return a
...
>>> with Sample() as sample:
...     sample.do_wrong_job()
...
执行出错 ZeroDivisionError
division by zero <class 'ZeroDivisionError'>
<traceback object at 0x00000212FDFFE208>

2. contextlib

2.1. 引言

  • 在上面的例子中,我们只是想构建一个上下文管理器,却写了一个复杂的类;
  • 如果只是要实现一个简单的功能,写一个类未免有点过于繁杂,如果只写一个函数就可以实现上下文管理器就好了,这就需要 contextlib 出场了;

2.2. 什么是 contextlib

  • contextlib 是一个装饰器,只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。

2.3. contextlib 实现上下文管理器

  • 我们按照 contextlib 的协议来自己实现一个打开文件(with open)的上下文管理器。
import contextlib


@contextlib.contextmanager
def open_func(file_name):
    # __enter__方法
    print('open file:', file_name)
    file_handler = open(file_name, 'r')
    
    yield file_handler          # 这里的 yield 用于返回一个生成器
    
    # __exit__方法
    print('close file:', file_name)
    file_handler.close()
    return


with open_func(r'filename') as file_in:
    for line in file_in:
        print(line)

2.4. contextlib 实现捕获异常


>>> import contextlib
>>>
>>> @contextlib.contextmanager
... def open_func(file_name):
...     # __enter__方法
...     print('open file:', file_name, 'in __enter__')
...     try:
...         file_handler = open(file_name, 'r', encoding="utf-8")
...         yield file_handler
...     except Exception as exc:
...         # deal with exception
...         print(f'the exception was thrown {exc}')
...     finally:
...         print('close file:', file_name, 'in __exit__')
...         try:
...             file_handler.close()
...         except:
...             return
...         return
...
>>> with open_func(r'filename') as file_in:
...     for line in file_in:
...         1/0
...         print(line)
...
open file: C:\Users\Administrator\Desktop\jiaohuobao\queried_merchants.csv in __enter__
the exception was thrown division by zero
close file: C:\Users\Administrator\Desktop\jiaohuobao\queried_merchants.csv in __exit__

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