上下文管理器1

https://www.cnblogs.com/pyspark/articles/8819803.html
今天在逛stackoverflow的时候,发现了contextlib这个模块的的作用!而且今天成功将这个模块应用到了项目中,简直爽的飞起!特此整理一篇博客,分享给大家!

一.引言

我们在操作文件时最常用的就是使用with上下文管理器,这样会让代码的可读性更强而且错误更少,例如:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">with open('/tmp/a.txt', a) as file_obj:
file_obj.write("hello carson")</pre>

按照上述这样写的好处在于,在执行完毕缩进代码块后会自动关闭文件。同样的例子还有threading.Lock,如果不使用with,需要这样写:

[[图片上传中...(image-6c1140-1588745530624-9)]](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">import threading lock = threading.Lock() lock.acquire() try:
my_list.append(item) finally: lock.release()</pre>

[[图片上传中...(image-5093b0-1588745530624-8)]](javascript:void(0); "复制代码")

如果使用with,那就会非常简单:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">with lock:
my_list.append(item)</pre>

创建上下文管理实际就是创建一个类,添加enterexit方法。下面我们来实现open的上下文管理功能:

[[图片上传中...(image-a74c38-1588745530624-7)]](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">class OpenContext(object):

def __init__(self, filename, mode):
    self.fp = open(filename, mode)

def __enter__(self): return self.fp

def __exit__(self, exc_type, exc_val, exc_tb):
    self.fp.close()

with OpenContext('/tmp/a.txt', 'a') as file_obj:
file_obj.write("hello 6666")</pre>

[[图片上传中...(image-811ec0-1588745530624-6)]](javascript:void(0); "复制代码")

上面我们自定义上下文管理器确实很方便,但是Python标准库还提供了更加易用的上下文管理器工具模块contextlib,它是通过生成器实现的,我们不需要再创建类以及enterexit这两个特俗的方法:

[[图片上传中...(image-751f84-1588745530624-5)]](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">from contextlib import contextmanager

@contextmanager
def make_open_context(filename, mode):
fp = open(filename, mode) try: yield fp finally:
fp.close()

with make_open_context('/tmp/a.txt', 'a') as file_obj:
file_obj.write("hello carson666")</pre>

[[图片上传中...(image-f9ad77-1588745530623-4)]](javascript:void(0); "复制代码")

在上文中,yield关键词把上下文分割成两部分:yield之前就是init中的代码块;yield之后其实就是exit中的代码块,yield生成的值会绑定到with语句as子句中的变量,例如在上面的例子中,yield生成的值是文件句柄对象fp,在下面的with语句中,会将fp和file_obj绑定到一起,也就是说file_obj此时就是一个文件句柄对象,那么它就可以操作文件了,因此就可以调用file_obj.write("hello carson666"),另外要注意的是如果yield没有生成值,那么在with语句中就不需要写as子句了,后面会结合具体的例子详解。

案例一:

[[图片上传中...(image-9e50ac-1588745530623-3)]](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"># * coding:utf-8 * from contextlib import contextmanager """ contextmanager给了我们一个机会,即将原来不是上下文管理器的类变成了一个
上下文管理器,例如这里的MyResource类 """
class MyResource:
def query(self):
print("query data")

@contextmanager
def make_myresource():
print("connect to resource") yield MyResource()
print("connect to resource")

with make_myresource() as r:
r.query()</pre>

[[图片上传中...(image-884e16-1588745530623-2)]](javascript:void(0); "复制代码")

上面的例子就充分体现了contextmanager的强大作用,将一个不是上下问管理器的类 MyResource变成了一个上下文管理器,这样做的好处在于,我们就可以在执行真正的核心代码之前可以执行一部分代码,然后在执行完毕后,又可以执行一部分代码,这种场景在实际需求中还是很常见的。上面yield MyResource() 生成了一个实例对象,然后我们可以在with语句中调用类中的方法。看看最终的打印结果:

image

上面这样写的好处还有:假如MyResource是Flask或者其他第三方插件提供给我们的类库,如果使用自定义上下文管理器,那么就要使用手动去修改源码,在原代码的基础上添加enter和exit方法,这样做肯定不合适;现在我们可以在类MyResource的外部使用contextmanager将该类包装成为一个上下文管理器,这样既可以调用类中的方法,又可以在执行核心代码前后再执行一些相关的语句。

案例二需求

我现在想打印 《且将生活一饮而尽》 也就是说我们要打印《 和 》;这就体现了上下文管理器的一个用法:我仅仅就是需要在我的核心代码前面和后面各执行一段代码而已:

[[图片上传中...(image-4cb8f6-1588745530623-1)]](javascript:void(0); "复制代码")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"># * coding:utf-8 * from contextlib import contextmanager

@contextmanager
def book_mark():
print('《', end="") yield print('》', end="")

with book_mark():
# 核心代码
print('且将生活一饮而尽', end="")</pre>

[[图片上传中...(image-e55f7e-1588745530623-0)]](javascript:void(0); "复制代码")

打印结果:

[图片上传中...(image-dfd904-1588745530625-15)]

看看实际的例子,我们通常在SQLAlchemy中使用db.session.commit(),既然有commit,那就需要做一个事务的处理。因此我们需要使用try except来处理异常,如下图所示:

[图片上传中...(image-718eb2-1588745530625-14)]

一般在应用程序中,我们都有很多个db.session.commit(),那如果都要使用try来处理异常,那就太麻烦了。我们需要做的是在核心代码之前使用try,然后在核心代码执行完毕之后,加上except。因此这就使用到了上下文管理器,此时这个db是一个第三方类库SQLAlchemy,那我们如何去为它新增加一个方法呢?那我们就继承SQLAlchemy,首先导入,然后取名字:

[图片上传中...(image-f97831-1588745530625-13)]

image

上面我们就为SQLAlchemy新增了一个auto_commit方法,主要实现的是自动commit()和rollback();并将其变成了一个上下文管理器。

那么原始的代码就可以变成如下的:

image

如下,我们在保存user的时候也使用到了db.session.commit();所以也可以改写:

[图片上传中...(image-e53a3a-1588745530625-11)]

修改如下:

[图片上传中...(image-d17c94-1588745530625-10)]

不是上下文管理器的类,是不能使用with语句调用的。

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