追踪函数调用关系

在我们执行拥有复杂函数调用关系的一段python程序时,我们希望能够清楚的知道他们之间的调用关系以及在调用过程中传入的参数信息和返回值,这些信息对于我们分析程序的行为和bug会很有帮助。

我希望能实现一个装饰器,用于追踪函数的调用关系,下面是这个装饰器的简单实现

import sys
from functools import wraps


class FuncTrace:
    indent = 0
    def __init__(self, stream=sys.stdout, indent_step=2, is_show_res=True):
        self.steam = stream
        self.indent_step = indent_step
        self.is_show_res = is_show_res

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            curr_indent = ' '*FuncTrace.indent
            # 参数信息
            arg_lst = [repr(item) for item in args]
            kwarg_lst = ['{key}={value}'.format(key=key, value=value) for key, value in kwargs.items()]
            arg_str = ','.join(arg_lst + kwarg_lst)
            msg = "{curr_indent}{func_name}({arg})\n".format(curr_indent=curr_indent, func_name=func.__name__, arg=arg_str)
            self.steam.write(msg)

            # 下一次调用时, 增加缩进
            FuncTrace.indent += self.indent_step
            result = func(*args, **kwargs)
            FuncTrace.indent -= self.indent_step    # 调用结束后减少缩进

            if self.is_show_res:
                msg = "{indent}return {result}\n".format(indent=curr_indent, result=result)
                self.steam.write(msg)

            return result
        return wrapper

调用函数时的参数信息十分关键,因此将args和kwargs拼接在一起输出,不足之处在于,如果参数里的数据内容较多,比如一个参数是列表,列表里有超过100个元素,那么现有的这种写法会将这100个元素都输出,很影响输出结果的展示,这是一个可以优化的点。

通过在输出信息中增加缩进,来表现函数之间的调用关系,在函数执行前增加缩进,函数执行结束后减少缩进。

默认输出流是sys.stdout, 你也可以将它设置为文件或者log, 下面是这个装饰器的用法展示

def func_c(number):
    if number % 3 == 0:
        return number**2

    return number*2

@FuncTrace()
def func_b(number):
    if number % 2 == 0:
        return number*2
    else:
        return func_c(number)

@FuncTrace()
def func_a(lst):
    result = []
    for i in lst:
        result.append(func_b(i))

    return result


lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
func_a(lst)

程序输出结果

func_a([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
  func_b(1)
    func_c(1)
    return 2
  return 2
  func_b(2)
  return 4
  func_b(3)
    func_c(3)
    return 9
  return 9
  func_b(4)
  return 8
  func_b(5)
    func_c(5)
    return 10
  return 10
  func_b(6)
  return 12
  func_b(7)
    func_c(7)
    return 14
  return 14
  func_b(8)
  return 16
  func_b(9)
    func_c(9)
    return 81
  return 81
  func_b(10)
  return 20
return [2, 4, 9, 8, 10, 12, 14, 16, 81, 20]

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案