python 黑魔法 ---上下文管理器(contextor)
所谓上下文
计算机上下文(Context)对于我而言,一直是一个很抽象的名词。就像形而上一样,经常听见有人说,但是无法和现实认知世界相结合。
最直观的上下文,莫过于小学的语文课,经常会问联系上下文,推测...,回答...,表明作者...。文章里的上下文比较好懂,无非就是前与后。
直到了解了计算机的执行状态,程式的运行,才稍微对计算机的上下文(context)有了一定的认识,多半还是只可意会,不可言传。本文所讨论的上下文,简而言之,就是程式所执行的环境状态,或者说程式运行的情景。
关于上下文的定义,我就不在多言,具体通过程式来理解。既然提及上下文,就不可避免的涉及Python中关于上下文的魔法,即上下文管理器(contextor)。
资源的创建和释放场景
上下文管理器的常用于一些资源的操作,需要在资源的获取与释放相关的操作,一个典型的例子就是数据库的连接,查询,关闭处理。先看如下一个例子:
class Database(object):
def __init__(self):
self.connected = False
def connect(self):
self.connected = True
def close(self):
self.connected = False
def query(self):
if self.connected:
return 'query data'
else:
raise ValueError('DB not connected ')
def handle_query():
db = Database()
db.connect()
print 'handle --- ', db.query()
db.close()
def main():
handle_query()
if __name__ == '__main__':
main()
上述的代码很简单,针对Database这个数据库类,提供了connect query 和close 三种常见的db交互接口。客户端的代码中,需要查询数据库并处理查询结果。当然这个操作之前,需要连接数据库(db.connect())和操作之后关闭数据库连接( db.close())。上述的代码可以work,可是如果很多地方有类似handle_query的逻辑,连接和关闭这样的代码就得copy很多遍,显然不是一个优雅的设计。
对于这样的场景,在python黑魔法—装饰器中有讨论如何优雅的处理。下面使用装饰器进行改写如下:
class Database(object):
...
def dbconn(fn):
def wrapper(*args, **kwargs):
db = Database()
db.connect()
ret = fn(db, *args, **kwargs)
db.close()
return ret
return wrapper
@dbconn
def handle_query(db=None):
print 'handle --- ', db.query()
def main():
...
编写一个dbconn的装饰器,然后在针对handle_query进行装饰即可。使用装饰器,复用了很多数据库连接和释放的代码逻辑,看起来不错。
装饰器解放了生产力。可是,每个装饰器都需要事先定义一下db的资源句柄,看起来略丑,不够优雅。
优雅的With as语句
Python提供了With语句语法,来构建对资源创建与释放的语法糖。给Database添加两个魔法方法:
class Database(object):
...
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
然后修改handle_query函数如下:
def handle_query():
with Database() as db:
print 'handle ---', db.query()
在Database类实例的时候,使用with语句。一切正常work。比起装饰器的版本,虽然多写了一些字符,但是代码可读性变强了。
上下文管理协议
前面初略的提及了上下文,那什么又是上下文管理器呢?与python黑魔法—迭代器类似,实现了迭代协议的函数/对象即为迭代器。实现了上下文协议的函数/对象即为上下文管理器。
迭代器协议是实现了__iter__方法。上下文管理协议则是__enter__和__exit__。对于如下代码结构:
class Contextor:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass
contextor = Contextor()
with contextor [as var]:
with_body
Contextor 实现了__enter__和__exit__这两个上下文管理器协议,当Contextor调用/实例化的时候,则创建了上下文管理器contextor。类似于实现迭代器协议类调用生成迭代器一样。
配合with语句使用的时候,上下文管理器会自动调用__enter__方法,然后进入运行时上下文环境,如果有as 从句,返回自身或另一个与运行时上下文相关的对象,值赋值给var。当with_body执行完毕退出with语句块或者with_body代码块出现异常,则会自动执行__exit__方法,并且会把对于的异常参数传递进来。如果__exit__函数返回True。则with语句代码块不会显示的抛出异常,终止程序,如果返回None或者False,异常会被主动raise,并终止程序。
大致对with语句的执行原理总结Python上下文管理器与with语句:
执行 contextor 以获取上下文管理器
加载上下文管理器的 exit() 方法以备稍后调用
调用上下文管理器的 enter() 方法
如果有 as var 从句,则将 enter() 方法的返回值赋给 var
执行子代码块 with_body
调用上下文管理器的 exit() 方法,如果 with_body 的退出是由异常引发的,那么该异常的 type、value 和 traceback 会作为参数传给 exit(),否则传三个 None
如果 with_body 的退出由异常引发,并且 exit() 的返回值等于 False,那么这个异常将被重新引发一次;如果 exit() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码
了解了with语句和上下文管理协议,或许对上下文有了一个更清晰的认识。即代码或函数执行的时候,调用函数时候有一个环境,在不同的环境调用,有时候效果就不一样,这些不同的环境就是上下文。例如数据库连接之后创建了一个数据库交互的上下文,进入这个上下文,就能使用连接进行查询,执行完毕关闭连接退出交互环境。创建连接和释放连接都需要有一个共同的调用环境。不同的上下文,通常见于异步的代码中。
上下文管理器工具
通过实现上下文协议定义创建上下文管理器很方便,Python为了更优雅,还专门提供了一个模块用于实现更函数式的上下文管理器用法。
import contextlib
@contextlib.contextmanager
def database():
db = Database()
try:
if not db.connected:
db.connect()
yield db
except Exception as e:
db.close()
def handle_query():
with database() as db:
print 'handle ---', db.query()
使用contextlib 定义一个上下文管理器函数,通过with语句,database调用生成一个上下文管理器,然后调用函数隐式的__enter__方法,并将结果通yield返回。最后退出上下文环境的时候,在except代码块中执行了__exit__方法。当然我们可以手动模拟上述代码的执行的细节。
In [1]: context = database() # 创建上下文管理器
In [2]: context
In [3]: db = context.__enter__() # 进入with语句
In [4]: db # as语句,返回 Database实例
Out[4]:
In [5]: db.query()
Out[5]: 'query data'
In [6]: db.connected
Out[6]: True
In [7]: db.__exit__(None, None, None) # 退出with语句
In [8]: db
Out[8]:
In [9]: db.connected
Out[9]: False
上下文管理器的用法
既然了解了上下文协议和管理器,当然是运用到实践啦。通常需要切换上下文环境,往往是在多线程/进程这种编程模型。当然,单线程异步或者协程的当时,也容易出现函数的上下文环境经常变动。
异步式的代码经常在定义和运行时存在不同的上下文环境。此时就需要针对异步代码做上下文包裹的hack。看下面一个例子:
import tornado.ioloop
ioloop = tornado.ioloop.IOLoop.instance()
def callback():
print 'run callback'
raise ValueError('except in callback')
def async_task():
print 'run async task'
ioloop.add_callback(callback=callback)
def main():
try:
async_task()
except Exception as e:
print 'exception {}'.format(e)
print 'end'
main()
ioloop.start()
运行上述代码得到如下结果
run async task
end
run callback
ERROR:root:Exception in callback
Traceback (most recent call last):
...
raise ValueError('except in callback')
ValueError: except in callback
主函数中main中,定义了异步任务函数async_task的调用。async_task中异常,在except中很容易catch,可是callback中出现的异常,则无法捕捉。原因就是定义的时候上下文为当前的线程执行环境,而使用了tornado的ioloop.add_callback方法,注册了一个异步的调用。当callback异步执行的时候,他的上下文已经和async_task的上下文不一样了。因此在main的上下文,无法catch异步中callback的异常。
下面使用上下文管理器包装如下:
class Contextor(object):
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if all([exc_type, exc_val, exc_tb]):
print 'handler except'
print 'exception {}'.format(exc_val)
return True
def main():
with tornado.stack_context.StackContext(Contextor):
async_task()
运行main之后的结果如下:
run async task
handler except
run callback
handler except
exception except in callback
可见,callback的函数的异常,在上下文管理器Contextor中被处理了,也就是说callback调用的时候,把之前main的上下文保存并传递给了callback。当然,上述的代码也可以改写如下:
@contextlib.contextmanager
def contextor():
try:
yield
except Exception as e:
print 'handler except'
print 'exception {}'.format(e)
finally:
print 'release'
def main():
with tornado.stack_context.StackContext(contextor):
async_task()
效果类似。当然,也许有人会对StackContext这个tornado的模块感到迷惑。其实他恰恰应用上下文管理器的魔法的典范。查看StackContext的源码,实现非常精秒,非常佩服tornado作者的编码设计能力。至于StackContext究竟如何神秘,已经超出了本篇的范围,将会在介绍tonrado异步上下文管理器中介绍。
参见:http://www.cnblogs.com/wumingxiaoyao/p/7132181.html
python 黑魔法 ---上下文管理器(contextor)的更多相关文章
- 谈一谈Python的上下文管理器
经常在Python代码中看到with语句,仔细分析下,会发现这个with语句功能好强,可以自动关闭资源.这个在Python中叫上下文管理器Context Manager.那我们要怎么用它,什么时候用它 ...
- python contextlib 上下文管理器
1.with操作符 在python中读写文件,可能需要这样的代码 try-finally读写文件 file_text = None try: file_text = open('./text', 'r ...
- python使用上下文管理器实现sqlite3事务机制
如题,本文记录如何使用python上下文管理器的方式管理sqlite3的句柄创建和释放以及事务机制. 1.python上下文管理(with) python上下文管理(context),解决的是这样一类 ...
- 【Python】 上下文管理器和contextlib
上下文管理器 一直对python中的上下文管理比较迷惑,趁着今天研究SQLAlchemy顺便看了一下,感觉稍微清楚了一点.http://www.cnblogs.com/chenny7/p/421344 ...
- python的上下文管理器-1
reference:https://zhuanlan.zhihu.com/p/26487659 来看看如何正确关闭一个文件. 普通版: def m1(): f = open("output. ...
- Python 的上下文管理器是怎么设计的?
花下猫语:最近,我在看 Python 3.10 版本的更新内容时,发现有一个关于上下文管理器的小更新,然后,突然发现上下文管理器的设计 PEP 竟然还没人翻译过!于是,我断断续续花了两周时间,终于把这 ...
- python的上下文管理器
直接上代码: f = open('123.txt','w') try: f.write('hello world') except Exception: pass finally: f.close() ...
- 【Python】【上下文管理器】
"""#[备注]#1⃣️try :仅当try块中没有异常抛出时才运行else块.#2⃣️for:仅当for循环运行完毕(即for循环没有被break语句终止)才运行els ...
- Python中的上下文管理器和with语句
Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先, ...
随机推荐
- 也来说说C#异步委托 (转自 Rising_Sun)
前些日子,看到园子里面有人用老王喝茶的例子讲解了一下同步和异步,虽然没有代码实现,但是能够通俗易懂的讲解了同步.异步.阻塞.非阻塞的关系了,今天借题发挥,用一个热水器加热洗澡的例子来具体演示一下C#使 ...
- IOS-高仿bilibili项目
高仿bilibili项目成长之路 (logo) 高仿bilibili项目 Github链接:(https://github.com/MichaelHuyp/Bilibili_Wuxianda) 目前完 ...
- 从 TWAIN 设备中扫描图像
转自(http://yonsm.net/scan-images-from-a-twain-device/) 一.简介 TWAIN 数据源管理程序 (DSM) 工业标准的软件库,用于从静态图像设备提取图 ...
- Centos7 防火墙常用命令 开启 关闭防火墙
如果你的系统上没有安装使用命令安装 #yum install firewalld //安装firewalld 防火墙 开启服务 # systemctl start firewalld.service ...
- Facebook的工程师文化——《打造facebook》读后感
在今年北京的QCon大会上听了facebook早期中国籍工程师王淮的演讲,受益匪浅,主题是如何打造高效能团队,主要介绍他在facebook的一些经历和管理上的经验分享.现在的他是一名天使投资人,投资的 ...
- Git 之 git原理简介
这里只是很简单.超简单的介绍下git,为的是方便记忆: 本地仓库分为三个部分:工作区.暂存区.仓库区,其中暂存区和仓库区属于版本区. 对于文件的操作,需要从工作区----> 暂存区 ----&g ...
- I.MX6 Manufacturing Tool V2 (MFGTool2) ucl2.xml hacking
<!-- * Copyright (C) 2010-2013, Freescale Semiconductor, Inc. All Rights Reserved. * The CFG elem ...
- Spinner的用法
1.先自定义一个数组 2.在new一个ArrayAdapter方法: ArrayAdapter<String> adapter = new ArrayAdapter<String&g ...
- 使用GitHub进行项目创建——初级,非指令版,纯软件操作
主要步骤如下: 1.申请一个GitHub账号,官网按照步骤来就行 2.下载一个GitHub DeskTop(https://desktop.github.com/),命令什么的以后说不定会写把 3.创 ...
- 第2本MATLAB书
前面看的<DSP using MATLAB>后面还有两章,其内容太难了,看不下去,暂时放放: 因为工作中需要MATLAB和电磁场的相关知识,从网上找了本 初步翻了翻,里面有代码有图片,英文 ...