Python代码:关于循环/条件执行跟踪的信息

Python代码:关于循环/条件执行跟踪的信息,python,python-3.x,unit-testing,execution,Python,Python 3.x,Unit Testing,Execution,我想根据完成时执行的循环和条件来获取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()之后,我需要一

我想根据完成时执行的循环和条件来获取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函数自动获取此信息


我知道“覆盖率”python模块返回“分支覆盖率”信息。但我不确定在这种情况下如何使用它?

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

例子
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', True), ...)]
注意
ast.unpasse
用于获取条件的字符串表示形式。它是在Python3.9中引入的。如果您想使用较旧版本的Python,可能需要安装第三方软件包,然后在函数中使用它。否则,
trace_conditions
将不会返回条件的字符串表示形式

TL;博士 主意 基本上,我们希望通过编程方式将捕捉器添加到函数的代码中。例如,
print
catchers可以如下所示:

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
唯一需要解释的是
globals\uu=inspect.stack()[1][0].f\u globals
。为了编译一个新函数,我们需要为python提供该函数使用的所有模块(例如,它可能使用
math
numpy
django
,等等)。和
inspect.stack()[1][0].f_globals
只需获取调用函数模块中导入的所有内容

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
警告

# 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\u conditions.py
中修改代码,也可以在
test.py
中添加
import math

_构建语法树 在这里,我们首先获取函数using的源代码,然后使用语法树解析它。不幸的是,如果函数的源代码是从
decorator
调用的,python就无法检查它,因此使用这种方法似乎不可能使用方便的decorator

_注射捕集器 在这个函数中,我们遍历给定的语法树,找到
while
if
语句,然后在它们之前或之后插入捕捉器
ast
模块有方法,但它只返回节点本身(没有父节点),所以我实现了稍微更改的
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, _create_catcher(node, catcher_type))
    ast.fix_missing_locations(tree)
最后,我们调用
ast.fix_missing_locations
函数,该函数有助于正确填写技术字段,如
lineno
以及编译代码所需的其他字段。通常,在修改语法树时需要使用它

捕捉
elif
语句 有趣的是python的ast语法中没有
elif
语句,所以它只有
if-else
语句。
ast.If
节点具有包含
If
主体表达式的字段
body
,以及包含
else
块表达式的字段
orelse
。而
elif
case则简单地由
ast.If
节点表示,节点位于
orelse
字段内。这一事实反映在功能上

捕捉器(和
\u聚集\u条件
) 有几种方法可以捕获条件,最简单的方法是只
打印它,但是如果您想稍后在python代码中处理它们,这种方法将不起作用。一种简单的方法是创建一个全局空列表,在函数执行期间,您将在其中附加条件及其值。但是,我认为该解决方案在名称空间中引入了一个新名称,它可能会与函数中的本地名称混淆,因此我决定,
产生
条件及其信息应该更安全


函数
\u collect\u conditions
添加了一个带有注入的
yield
语句的包装函数,它只收集所有生成的条件并返回函数和条件的结果。

,我想在不使用附加参数插入原始python函数的情况下执行此操作,您能详细说明一下吗?因为任何类型的跟踪都需要检测。不一定要更改函数本身的源代码,但一定要使用Python内置的跟踪支持。其他跟踪支持也不错:)您的分支跟踪需求不是很清楚,或者我怀疑您没有完全理解其中的复杂性。例如,应如何记录复合条件?例如:
如果不是(a或b):
。那
elif
else
块呢?条件表达式也是您需要跟踪的分支决策吗?所有这些问题都使我们很难写出一个有针对性的答案。可能是使用
sys.set_trace()
和跟踪操作码就足够了,但即使如此,理解如果和while如何导致特定的跳转组合也不是一件小事。因此,我最多只能指向和的文档。