微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Python 代码:有关循环/条件执行跟踪的信息 示例主函数trace_conditions_build_syntactic_tree_inject_catchers捕获 elif 语句捕手和_gather_conditions

如何解决Python 代码:有关循环/条件执行跟踪的信息 示例主函数trace_conditions_build_syntactic_tree_inject_catchers捕获 elif 语句捕手和_gather_conditions

我想根据完成时执行的循环和条件来获取 python 函数的执行跟踪。但是,我想在不使用附加参数检测原始 python 函数的情况下执行此操作。例如:

def foo(a: int,b: int):
    while a:
        a = do_something()
        if b:
            a = do_something()


if __name__ == "__main__":
    foo(a,b)

在执行 foo() 之后,我想要一个类似于以下内容的执行跟踪: [while: true,if:false,while: true,if: true,while: false,...] 记录了代码中条件评估的顺序。有没有办法为任意python函数自动获取这些信息?

我了解“Coverage”python 模块返回“Branch coverage”信息。但我不确定如何在这种情况下使用它?

解决方法

您可以将 trace_conditions.py 用作起点,并在需要时对其进行修改。

示例

问题中定义的

foo 函数用于以下示例:

from trace_conditions import trace_conditions

# (1) This will just print conditions
traced_foo = trace_conditions(foo)
traced_foo(a,b)
# while c -> True
# if d -> True
# ...

# (2) This will return conditions
traced_foo = trace_conditions(foo,return_conditions=True)
result,conditions = traced_foo(a,b)
# conditions = [('while','c',True),('if','d',...)]

注意ast.unparse 用于获取条件的字符串表示。它是在 Python 3.9 中引入的。如果您想使用旧版本的 Python,也许您需要安装 3rd 方包 astunparse,然后在函数 _condition_to_string 中使用它。否则 trace_conditions 不会返回条件的字符串表示。

TL;DR

想法

基本上,我们希望以编程方式将捕获器添加到函数的代码中。例如,print 接球手可能如下所示:

while x > 5:
    print('while x > 5',x > 5)  # <-- print condition after while
    # do smth

print('if x > 5',x > 5)  # <-- print condition before if
if x > 5:
    # do smth

所以,主要思想是在python中使用代码自省工具(inspectastexec)。

实施

这里我简单解释一下trace_conditions.py中的代码:

主函数trace_conditions

main 函数不言自明,简单地反映了整个算法: (1) 构建句法树; (2) 注入条件捕获器; (3) 编译新函数。

def trace_conditions(
        func: Callable,return_conditions=False):
    catcher_type = 'yield' if return_conditions else 'print'

    tree = _build_syntactic_tree(func)
    _inject_catchers(tree,catcher_type)
    func = _compile_function(tree,globals_=inspect.stack()[1][0].f_globals)

    if return_conditions:
        func = _gather_conditions(func)
    return func

唯一需要解释的是globals_=inspect.stack()[1][0].f_globals。为了编译一个新函数,我们需要为 python 提供该函数使用的所有模块(例如,它可能使用 mathnumpydjango 等...) .而 inspect.stack()[1][0].f_globals 只是获取调用函数模块中导入的所有内容。

注意!

# math_pi.py
import math

def get_pi():
   return math.pi


# test.py
from math_pi import get_pi
from trace_conditions import trace_conditions

traced = trace_conditions(get_pi)
traced()  # Error! Math is not imported in this module

要解决它,您可以修改 trace_conditions.py 中的代码,也可以在 import math 中添加 test.py

_build_syntactic_tree

这里我们首先使用 inspect.getsource 获取函数的源代码,然后使用 ast.parse 在语法树中解析它。不幸的是,如果从 decorator 调用,python 无法检查函数的源代码,因此使用这种方法似乎无法使用方便的装饰器。

_inject_catchers

在这个函数中,我们遍历给定的语法树,找到 whileif 语句,然后在它们之前或之后注入捕获器。 ast 模块有方法 walk,但它只返回节点本身(没有父节点),所以我实现了稍微改变的 walk 版本,它也返回父节点。如果我们想在 if 之前插入 catcher,我们需要知道 parent。

def _inject_catchers(tree,catcher_type):
    for parent,node in _walk_with_parent(tree):
        if isinstance(node,ast.While):
            _catch_after_while(node,_create_catcher(node,catcher_type))
        elif isinstance(node,ast.If):
            _catch_before_if(parent,node,catcher_type))
    ast.fix_missing_locations(tree)

最后我们调用 ast.fix_missing_locations 函数,它有助于正确填写技术字段,例如 lineno 和其他编译代码所需的字段。通常,在修改句法树时需要用到它。

捕获 elif 语句

有趣的是,python 在它的 ast 语法中没有 elif 语句,所以它只有 if-else 语句。 ast.If 节点具有包含 body 主体表达式的字段 if 和包含 orelse 块表达式的字段 else。而 elif 情况仅由 ast.If 字段内的 orelse 节点表示。这一事实反映在函数 _catch_before_if 中。

捕手(和_gather_conditions

有几种方法可以捕获条件,最简单的就是 print 它,但是如果您想稍后在 Python 代码中处理它们,这种方法将不起作用。一种直接的方法是拥有一个全局空列表,您将在执行函数期间在其中附加条件及其值。但是,我认为此解决方案在命名空间中引入了一个新名称,该名称可能会与函数内的本地名称混淆,因此我决定它应该对 yield 条件及其信息更安全。

函数 _gather_conditions 为函数添加了一个包装器,其中包含注入的 yield 语句,它只是收集所有产生的条件并返回函数和条件的结果。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。