欧博亚洲手机版下载:pythonic context manager知多少

admin 4个月前 (06-29) 科技 55 0

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

With statement

Context Managers

contextlib

what-is-the-python-with-statement-desIGned-for

Transforming Code into Beautiful, Idiomatic Python

,

欧博电脑版

欢迎进入欧博电脑版(Allbet Game):www.aLLbetgame.us,欧博网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

Sunbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明:欧博亚洲手机版下载:pythonic context manager知多少

网友评论

  • (*)

最新评论

标签列表

    文章归档

      站点信息

      • 文章总数:653
      • 页面总数:0
      • 分类总数:8
      • 标签总数:1146
      • 评论总数:283
      • 浏览总数:17261