pythonic context manager知多少
Context Managers 是我最喜欢的 python feature 之一,在恰当的时机使用 context manager 使代码更加简洁、清晰,更加安全,复用性更好,更加 pythonic。本文简单介绍一下其使用方法以及常见使用场景。
本文地址:https://www.cnblogs.com/xybaby/p/13202496.html
with statement and context manager
Python’s with statement supports the concept of a runtime context defined by a context manager
new statement "with" to the Python language to make it possible to factor out standard uses of try/finally statements.
在 pep0343 中,通过引入 context manager protocol 来支持 With statement , context manager 是用来管理 context(上下文)的,即保证程序要保持一种特定的状态 -- 无论是否发生异常。可以说,context manager 简化了对 try-finally 的使用,而且更加安全,更加便于使用。
Transforming Code into Beautiful, Idiomatic Python 中,指出了 context manager 的最显著的优点:
- Helps separate business logic from administrative logic
- Clean, beautiful tools for factoring code and improving code reuse
最广为人知的例子,就是通过 with statement 来读写文件,代码如下:
with open('test.txt') as f:
contect = f.read()
handle_content(content)
上面的代码几乎等价于
f = open('test.txt')
try:
contect = f.read()
handle_content(content)
finally:
f.close()
注意,上面的finally的作用就是保证file.close一定会被调用,也就是资源一定会释放。不过,很多时候,都会忘了去写这个finally,而 with statement 就彻底避免了这个问题。
从上述两段代码也可以看出,with statement 更加简洁,而且将核心的业务逻辑(从文件中读取、处理数据)与其他逻辑(打开、关系文件)相分离,可读性更强。
实现context manager protocol
一个类只要定义了__enter__、__exit__方法就实现了context manager 协议
object.__enter__(self)
Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.
object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.
If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.
__enter__方法在进入这个 context 的时候调用,返回值赋值给 with as X 中的 X
__exit__方法在退出 context 的时候调用,如果没有异常,后三个参数为 None。如果返回值为 True,则Suppress Exception,所以除非特殊情况都应返回 False。另外注意, __exit__方法本身不应该抛出异常。
例子:BlockGuard
在看c++代码(如mongodb源码)的时候,经常看见其用 RAII 实现BlockGuard, 用以保证在离开 Block 的时候执行某些动作,同时,也提供手段来取消执行。
下面用python实现一下:
class BlockGuard(object):
def __init__(self, fn, *args, **kwargs):
self._fn = fn
self._args = args
self._kwargs = kwargs
self._canceled = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if not self._canceled:
self._fn(*self._args, **self._kwargs)
self._fn = None
self._args = None
self._kwargs = None
return False
def cancel(self):
self._canceled = True
def foo():
print 'sth should be called'
def test_BlockGuard(cancel_guard):
print 'test_BlockGuard'
with BlockGuard(foo) as guard:
if cancel_guard:
guard.cancel()
print 'test_BlockGuard finish'
用yield实现context manager
标准库 contextlib 中提供了一些方法,能够简化我们使用 context manager,如 contextlib.contextmanager(func) 使我们
无需再去实现一个包含__enter__ __exit__方法的类。
The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.
例子如下:
from contextlib import contextmanager
@contextmanager
def managed_resource(*args, **kwds):
# Code to acquire resource, e.g.:
resource = acquire_resource(*args, **kwds)
try:
yield resource
finally:
# Code to release resource, e.g.:
release_resource(resource)
>>> with managed_resource(timeout=3600) as resource:
... # Resource is released at the end of this block,
... # even if code in the block raises an exception
需要注意的是:
- 一定要写 try finally,才能保证
release_resource逻辑一定被调用 - 除非特殊情况,不再 catch exception,这就跟
__exit__一般不返回True一样
例子: no_throw
这是业务开发中的一个需求, 比如观察者模式,不希望因为其中一个观察者出了 trace 就影响后续的观察者,就可以这样做:
from contextlib import contextmanager
@contextmanager
def no_throw(*exceptions):
try:
yield
except exceptions:
pass
def notify_observers(seq):
for fn in [sum, len, max, min]:
with no_throw(Exception):
print "%s result %s" % (fn.__name__, fn(seq))
if __name__ == '__main__':
notify_observers([])
在python 3.x 的 contexlib 中,就提供了一个contextlib.suppress(*exceptions), 实现了同样的效果。
context manager 应用场景
context manager 诞生的初衷就在于简化 try-finally,因此就适合应用于在需要 finally 的地方,也就是需要清理的地方,比如
- 保证资源的安全释放,如 file、lock、semaphore、network connection 等
- 临时操作的复原,如果一段逻辑有 setup、prepare,那么就会对应 cleanup、teardown。
对于第一种情况,网络连接释放的例子,后面会结合 pymongo 的代码展示。
在这里先来看看第二种用途:保证代码在一个临时的、特殊的上下文(context)中执行,且在执行结束之后恢复到之前的上下文环境。
改变工作目录
from contextlib import contextmanager
import os
@contextmanager
def working_directory(path):
current_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(current_dir)
with working_directory("data/stuff"):
pass
临时文件、文件夹
很多时候会产生一堆临时文件,比如build的中间状态,这些临时文件都需要在结束之后清除。
from tempfile import mkdtemp
from shutil import rmtree
@contextmanager
def temporary_dir(*args, **kwds):
name = mkdtemp(*args, **kwds)
try:
yield name
finally:
shutil.rmtree(name)
with temporary_dir() as dirname:
pass
重定向标准输出、标准错误
@contextmanager
def redirect_stdout(fileobj):
oldstdout = sys.stdout
sys.stdout = fileobj
try:
yield fieldobj
finally:
sys.stdout = oldstdout
在 python3.x 中,已经提供了 contextlib.redirect_stdout contextlib.redirect_stderr 实现上述功能
调整logging level
这个在查问题的适合非常有用,一般生产环境不会输出 debug level 的日志,但如果出了问题,可以临时对某些制定的函数调用输出debug 日志
from contextlib import contextmanager
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(ch)
@contextmanager
def change_log_level(level):
old_level = logger.getEffectiveLevel()
try:
logger.setLevel(level)
yield
finally:
logger.setLevel(old_level)
def test_logging():
logger.debug("this is a debug message")
logger.info("this is a info message")
logger.warn("this is a warning message")
with change_log_level(logging.DEBUG):
test_logging()
pymongo中的context manager使用
在 pymongo 中,封装了好几个 context manager,用以
- 管理 semaphore
- 管理 connection
- 资源清理
而且,在 pymongo 中,给出了嵌套使用 context manager 的好例子,用来保证 socket 在使用完之后一定返回连接池(pool)。
# server.py
@contextlib.contextmanager
def get_socket(self, all_credentials, checkout=False):
with self.pool.get_socket(all_credentials, checkout) as sock_info:
yield sock_info
# pool.py
@contextlib.contextmanager
def get_socket(self, all_credentials, checkout=False):
sock_info = self._get_socket_no_auth()
try:
sock_info.check_auth(all_credentials)
yield sock_info
except:
# Exception in caller. Decrement semaphore.
self.return_socket(sock_info)
raise
else:
if not checkout:
self.return_socket(sock_info)
可以看到,server.get_socket 调用了 pool.get_socket, 使用 server.get_socket 的代码完全不了解、也完全不用关心 socket 的释放细节,如果把 try-except-finally-else 的逻辑移到所有使用socket的地方,代码就会很丑、很臃肿。
比如,在mongo_client 中需要使用到 socket:
with server.get_socket(all_credentials) as sock_info:
sock_info.authenticate(credentials)
references
what-is-the-python-with-statement-designed-for
Transforming Code into Beautiful, Idiomatic Python
pythonic context manager知多少的更多相关文章
- Android的Context Manager(服务管理器)源码剖析-android学习之旅(99)
Context Manager介绍 Context Manager对应的进程是servicemanager进程,它先于Service Server和服务客户端运行,进入接收IPC数据的待机状态,处理来 ...
- Python——with语句、context manager类型和contextlib库
目录 一.with语句 二.上下文管理器 三.contextlib模块 基本概念 上下文管理协议(Context Management Protocol) 包含方法 __enter__() 和 __e ...
- Python之 context manager
在context manager中,必须要介绍两个概念: with as... , 和 enter , exit. 下文将先介绍with语句,然后介绍 __enter__和exit, 最后介绍cont ...
- Python上下文管理器(context manager)
上下文管理器(context manager)是Python2.5开始支持的一种语法,用于规定某个对象的使用范围.一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存).它的语 ...
- Python Study(02)之 Context Manager
上下文管理器(context manager)是Python2.5开始支持的一种语法,用于规定某个对象的使用范围.一旦对象进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存).它 ...
- Python - Context Manager 上下文管理器
什么是上下文管理器 官方解释... 上下文管理器是一个对象 它定义了在执行 with 语句时要建立的运行时上下文 上下文管理器处理进入和退出所需的运行时上下文以执行代码块 上下文管理器通常使用 wit ...
- 谈谈一些有趣的CSS题目(三)-- 层叠顺序与堆栈上下文知多少
开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...
- 使用Memcached Session Manager扩展Session管理
>>Tomcat的session管理 在请求过程中首先要解析请求中的sessionId信息,然后将sessionId存储到request的参数列表中. 然后再从request获取sessi ...
- android 进程间通信---Service Manager(1)
Bind机制由4个部分组成.bind驱动,Client,ServiceManager &Service 1.Bind其实是一个基于linux系统的驱动,目的是为了实现内存共享. bind驱动的 ...
随机推荐
- Java 异常(一) 异常概述及其架构
Java 异常(一) 异常概述及其架构 一.异常概述 (一).概述 Java异常是Java提供的一种识别及响应错误的一致性机制.异常指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常 ...
- Java实现 LeetCode 643 子数组最大平均数 I(滑动窗口)
643. 子数组最大平均数 I 给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数. 示例 1: 输入: [1,12,-5,-6,50,3], k = 4 输出: 12.7 ...
- Java实现 蓝桥杯VIP 算法训练 摆动序列
问题描述 如果一个序列满足下面的性质,我们就将它称为摆动序列: 1. 序列中的所有数都是不大于k的正整数: 2. 序列中至少有两个数. 3. 序列中的数两两不相等: 4. 如果第i – 1个数比第i ...
- Java实现 LeetCode 147 对链表进行插入排序
147. 对链表进行插入排序 对链表进行插入排序. 插入排序的动画演示如上.从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示). 每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将 ...
- 阿里云高级技术专家空见: CDN的数据化之路
想要实现优质高速的互联网视频服务,一定离不开高质量的内容分发网络服务,就是我们常说的CDN,在10月13日云栖大会视频多媒体分论坛上,阿里云高级技术专家空见为大家讲解了CDN服务过程中,数据处理.安全 ...
- java关键字static用法详解
java中有53个关键字,其中包含2个保留字,这篇文章主要介绍一下static这个关键字. static在java中算是一个比较常见的关键字,有着多种用法,因此很有必要好好地了解一番. 一.定义 st ...
- Azure AD(四)知识补充-服务主体
一,引言 又到了新的一周了,也到了我新的分享的时间了,还记得上一周立得Flag,其中 “保证每周输出一篇文章” ,让我特别“在意”(这里用词不太恰当).主要是我的一个大学舍友,他突然问了我一个关于写博 ...
- JAVA多线程实现的三种方法
JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...
- Pants On Fire(链式前向星存图、dfs)
Pants On Fire 传送门:链接 来源:upc9653 题目描述 Donald and Mike are the leaders of the free world and haven't ...
- 13.Django-分页
使用Django实现分页器功能 要使用Django实现分页器,必须从Django中导入Paginator模块 from django.core.paginator import Paginator 假 ...