Python上下文语法with小述
发布日期:2021-07-25 13:04:52 浏览次数:11 分类:技术文章

本文共 12404 字,大约阅读时间需要 41 分钟。

Python上下文语法with小述

本文环境python3.5.2

上下文语法with

该语法糖主要便于在Python的编程过程中,能够有效管理防止编程过程中,对有关资源编程时忘记释放的问题,比如通过with来open一个文件,就不需要显式的在处理完成文件之后调用f.close方法,易于简洁编写相关代码。在Python中通过contextlib提供了两种的上下文的实现方式,分别是通过继承ContextDecorator的类的实现,通过contextmanager装饰器函数的实现。

with的上下文语法的实现方式

无论是哪种实现方式,都是通过调用了类的__enter__和__exit__函数来实现的上下文管理,这在Python的官方文档中也做了相关定义介绍,无论是通过ContextDecorator还是通过contextmanager都是基于该两个内建方法进行上下文文法的实现的。首先分析如下代码:

from contextlib import ContextDecorator, contextmanagerclass WithClass(ContextDecorator):    def __enter__(self):        print('__enter__')        return self    def __exit__(self, exc_type, exc_val, exc_tb):        print('__exit__')    def test_print(self):        print("test_print")with WithClass() as w:    w.test_print()
with对应的字节码模块的内容分析

我们首先分析一下该代码的生成对应的字节码如下:

2           0 LOAD_CONST               0 (0)              3 LOAD_CONST               1 (('ContextDecorator', 'contextmanager'))              6 IMPORT_NAME              0 (contextlib)              9 IMPORT_FROM              1 (ContextDecorator)             12 STORE_NAME               1 (ContextDecorator)             15 IMPORT_FROM              2 (contextmanager)             18 STORE_NAME               2 (contextmanager)             21 POP_TOP  4          22 LOAD_BUILD_CLASS             23 LOAD_CONST               2 (", line 4>)             26 LOAD_CONST               3 ('WithClass')             29 MAKE_FUNCTION            0             32 LOAD_CONST               3 ('WithClass')             35 LOAD_NAME                1 (ContextDecorator)             38 CALL_FUNCTION            3 (3 positional, 0 keyword pair)             41 STORE_NAME               3 (WithClass) 16          44 LOAD_NAME                3 (WithClass)             47 CALL_FUNCTION            0 (0 positional, 0 keyword pair)             50 SETUP_WITH              17 (to 70)             53 STORE_NAME               4 (w) 17          56 LOAD_NAME                4 (w)             59 LOAD_ATTR                5 (test_print)             62 CALL_FUNCTION            0 (0 positional, 0 keyword pair)             65 POP_TOP             66 POP_BLOCK             67 LOAD_CONST               4 (None)        >>   70 WITH_CLEANUP_START             71 WITH_CLEANUP_FINISH             72 END_FINALLY             73 LOAD_CONST               4 (None)             76 RETURN_VALUE

通过生成的字节码可知,16行和17行就是对with语言的解析,首先先加载一个名称WithClass,然后通过CALL_FUNCTION来调用WithClass类的__call__方法,接着便是调用了SETUP_WITH来生成一个对象并把它赋值给变量w,在17行就获取w然后调用了w的属性test_print方法,在17行执行完成后,就调用了WITH_CLEANUP_START和WITH_CLEANUP_FINISH方法其实就是调用了__exit__方法对应的函数,最后调用了END_FINALLY,至此一个with的语法就执行完成。

其中SETUP_WITH对应的字节码如下;

TARGET(SETUP_WITH) {        _Py_IDENTIFIER(__exit__); 												# 获取__exit__的名称        _Py_IDENTIFIER(__enter__); 												# 获取__enter__的名称        PyObject *mgr = TOP(); 													# 获取对应实例化的类        PyObject *exit = special_lookup(mgr, &PyId___exit__), *enter; 			# 先通过实例类去查找__exit__方法        PyObject *res;        if (exit == NULL) 														# 如果exit为空则报错            goto error;        SET_TOP(exit); 															# 把获取的exit方法放置在栈顶        enter = special_lookup(mgr, &PyId___enter__); 							# 通过类查找__enter__方法        Py_DECREF(mgr);         if (enter == NULL) 														# 如果为空则报错            goto error;        res = PyObject_CallFunctionObjArgs(enter, NULL); 						# 调用查找到的enter函数并传入null参数执行        Py_DECREF(enter);        if (res == NULL) 														# 如果执行的enter函数的返回值为空则报错            goto error;        /* Setup the finally block before pushing the result           of __enter__ on the stack. */        PyFrame_BlockSetup(f, SETUP_FINALLY, INSTR_OFFSET() + oparg,                           STACK_LEVEL());        PUSH(res); 																# 压入返回值        DISPATCH();    }

由此可知,首先就执行了对应类实例的__enter__函数,并获取了返回值,然后执行完成with包裹的内容之后就调用WITH_CLEANUP_START来清理包裹的所有的数据并调用exit函数;

TARGET(WITH_CLEANUP_START) {        /* At the top of the stack are 1-6 values indicating           how/why we entered the finally clause:           - TOP = None           - (TOP, SECOND) = (WHY_{RETURN,CONTINUE}), retval           - TOP = WHY_*; no retval below it           - (TOP, SECOND, THIRD) = exc_info()             (FOURTH, FITH, SIXTH) = previous exception for EXCEPT_HANDLER           Below them is EXIT, the context.__exit__ bound method.           In the last case, we must call             EXIT(TOP, SECOND, THIRD)           otherwise we must call             EXIT(None, None, None)           In the first three cases, we remove EXIT from the           stack, leaving the rest in the same order.  In the           fourth case, we shift the bottom 3 values of the           stack down, and replace the empty spot with NULL.           In addition, if the stack represents an exception,           *and* the function call returns a 'true' value, we           push WHY_SILENCED onto the stack.  END_FINALLY will           then not re-raise the exception.  (But non-local           gotos should still be resumed.)        */        PyObject *exit_func;        PyObject *exc = TOP(), *val = Py_None, *tb = Py_None, *res;  			# 检查执行完成信息        if (exc == Py_None) { 													# 如果没有执行报错            (void)POP(); 													            exit_func = TOP(); 													# 获取exit函数		            SET_TOP(exc); 														# 把执行信息置顶        }        else if (PyLong_Check(exc)) { 											# 检查执行结果            STACKADJ(-1);            switch (PyLong_AsLong(exc)) {            case WHY_RETURN:            case WHY_CONTINUE:                /* Retval in TOP. */                exit_func = SECOND();                SET_SECOND(TOP());                SET_TOP(exc);                break;            default:                exit_func = TOP();                SET_TOP(exc);                break;            }            exc = Py_None;        }        else {            PyObject *tp2, *exc2, *tb2;            PyTryBlock *block;            val = SECOND();            tb = THIRD();            tp2 = FOURTH();            exc2 = PEEK(5);            tb2 = PEEK(6);            exit_func = PEEK(7);            SET_VALUE(7, tb2);            SET_VALUE(6, exc2);            SET_VALUE(5, tp2);            /* UNWIND_EXCEPT_HANDLER will pop this off. */            SET_FOURTH(NULL);            /* We just shifted the stack down, so we have               to tell the except handler block that the               values are lower than it expects. */            block = &f->f_blockstack[f->f_iblock - 1];            assert(block->b_type == EXCEPT_HANDLER);            block->b_level--;        }        /* XXX Not the fastest way to call it... */        res = PyObject_CallFunctionObjArgs(exit_func, exc, val, tb, NULL); 				# 调用exit函数并将执行的结果传入执行        Py_DECREF(exit_func);        if (res == NULL) 																# 如果res为空则报错            goto error;        Py_INCREF(exc); /* Duplicating the exception on the stack */        PUSH(exc);        PUSH(res);        PREDICT(WITH_CLEANUP_FINISH);        DISPATCH();    }

此处的两个字节码执行就是主要的with上下文所做的主要内容,主要还是依赖于对象的__exit__与__enter__两个方法。

ContextDecorator的实现的简单浅析

由上一节的内容可知,with上下文主要依赖于__enter__和__exit__两个方法的实现,所以查看一下ContextDecorator的代码;

class ContextDecorator(object):    "A base class or mixin that enables context managers to work as decorators."    def _recreate_cm(self):        """Return a recreated instance of self.        Allows an otherwise one-shot context manager like        _GeneratorContextManager to support use as        a decorator via implicit recreation.        This is a private interface just for _GeneratorContextManager.        See issue #11647 for details.        """        return self                                         # 返回自己    def __call__(self, func):                               # 如果使用了装饰器来包装被调用函数        @wraps(func)        def inner(*args, **kwds):                           with self._recreate_cm():                       # 调用self的__enter__和__exit__方法                return func(*args, **kwds)                  # 然后调用被包装函数执行        return inner

所以在上述示例代码中的打印函数也可改写成如下;

@WithClass()def test_print_func():    print("test_print")test_print_func()

上述内容基本上就是ContextDecorator的全部内容,相对比较简单易于理解。

contextmanager的函数形式的上下文实现
def contextmanager(func):    """@contextmanager decorator.    Typical usage:        @contextmanager        def some_generator(
):
try: yield
finally:
This makes this: with some_generator(
) as
: equivalent to this:
try:
=
finally:
""" @wraps(func) def helper(*args, **kwds): return _GeneratorContextManager(func, args, kwds) # 装饰器生成_GeneratorContextManager类实例 return helper

由函数的注释可知,该函数实现的上下文依赖于协程来实现的,该函数的定义相对简单,直接生成_GeneratorContextManager类实例,

class _GeneratorContextManager(ContextDecorator):    """Helper for @contextmanager decorator."""    def __init__(self, func, args, kwds):        self.gen = func(*args, **kwds)                                          # 生成协程函数        self.func, self.args, self.kwds = func, args, kwds                      # 保存函数与传入参数等值        # Issue 19330: ensure context manager instances have good docstrings        doc = getattr(func, "__doc__", None)                                    # 获取函数文档说明        if doc is None:            doc = type(self).__doc__        self.__doc__ = doc                                                      # 设置文档说明        # Unfortunately, this still doesn't provide good help output when        # inspecting the created context manager instances, since pydoc        # currently bypasses the instance docstring and shows the docstring        # for the class instead.        # See http://bugs.python.org/issue19404 for more details.    def _recreate_cm(self):        # _GCM instances are one-shot context managers, so the        # CM must be recreated each time a decorated function is        # called        return self.__class__(self.func, self.args, self.kwds)                  # 实例化自己    def __enter__(self):        try:            return next(self.gen)                                               # 获取fun中yield返回的值        except StopIteration:            raise RuntimeError("generator didn't yield") from None    def __exit__(self, type, value, traceback):        if type is None:            try:                next(self.gen)                                                  # 如果执行没有错误则继续next            except StopIteration:                return                                                          # 如果是StopIteration则立即返回            else:                raise RuntimeError("generator didn't stop")                     # 否则返回生成器错误        else:            if value is None:                # Need to force instantiation so we can reliably                # tell if we get the same exception back                value = type()            try:                self.gen.throw(type, value, traceback)                raise RuntimeError("generator didn't stop after throw()")            except StopIteration as exc:                # Suppress StopIteration *unless* it's the same exception that                # was passed to throw().  This prevents a StopIteration                # raised inside the "with" statement from being suppressed.                return exc is not value            except RuntimeError as exc:                # Likewise, avoid suppressing if a StopIteration exception                # was passed to throw() and later wrapped into a RuntimeError                # (see PEP 479).                if exc.__cause__ is value:                    return False                raise            except:                # only re-raise if it's *not* the exception that was                # passed to throw(), because __exit__() must not raise                # an exception unless __exit__() itself failed.  But throw()                # has to raise the exception to signal propagation, so this                # fixes the impedance mismatch between the throw() protocol                # and the __exit__() protocol.                #                if sys.exc_info()[1] is not value:                    raise

由上述代码可知,该_GeneratorContextManager所包装的函数必须是生成器函数,其实现基于ContextDecorator类的实现思路实现。改造的示例代码如下;

@contextmanagerdef test_func():    w = WithClass()    try:        yield w    finally:        print("test_func finally")with test_func() as f:    f.test_print()

总结

本文的有关Python上下文语法分析的内容短小,相应的使用的规则并不复杂,主要还是通过实现__enter__和__exit__两个方法来实现上下文,达到便捷的编程,contextlib中还有其他模块可供使用,大家有兴趣可自行查阅。鉴于本人才疏学浅,如有疏漏请批评指正。

转载地址:https://blog.csdn.net/qq_33339479/article/details/87719826 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:locust压测工具:启动概述
下一篇:Python标准库queue模块原理浅析

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年03月08日 10时26分55秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

c语言对结构体排序中间变量,求助:c语言怎么实现结构体的排序? 总是弄不对啊... 2019-04-21
c语言宏定义只能在最前面吗,C语言宏定义注意事项 2019-04-21
android悬浮窗服务卡死,Android 悬浮窗兼容问题谈 2019-04-21
表格相关的html语言,HTML标记语言——表格标记 2019-04-21
web聊天界面html,PC端Web聊天界面+代码分享(HTML+CSS) 2019-04-21
cmake qt 添加路径 项目_CMake配置Qt工程 2019-04-21
使用python开发的软件协议_WEB开发——Python WSGI协议详解 2019-04-21
冰点下载器手机版apk_冰点文库下载器 2019-04-21
python信号采集代码_13行代码实现:Python实时视频采集(附源码) 2019-04-21
h5引入json_纯js直接引入json文件 2019-04-21
python格式化字符串总结_Python字符串处理方法总结 2019-04-21
python中true什么意思_python中的bool是什么意思 2019-04-21
jacobian 矩阵意义_Jacobian矩阵和Hessian矩阵的作用是什么? 2019-04-21
c++ jna 数据类型_JNA 使用总结 2019-04-21
python中如何遍历列表并将列表值赋予_python中如何实现遍历整个列表? 2019-04-21
apache php mysql架构图_Apache+PHP+MYSQL+Tomcat+JK架构设计技巧与应用实战 2019-04-21
xlnt库如何编译_最新mysql数据库源码编译安装。 2019-04-21
mysql 2003错误 10055_MYSQL无法连接---提示10055错误 2019-04-21
mysql redis缓存层_redis实现缓存的两种方式 2019-04-21
git 改local branch名字_用Git管理Latex写论文的工作流程 2019-04-21