如何在Python执行过程中获取某一函数调用的所有函数列表?
解决方案:追踪函数调用同时保留签名完整性
针对你需要追踪ConversionFunctions库中所有被调用函数(包括嵌套调用),同时还要保留原函数签名用于参数校验的需求,这里有几个可行的方案:
方案一:改进装饰器,保留原函数签名
装饰器导致签名变化的问题其实很容易解决,Python标准库的functools.wraps可以帮我们完整保留原函数的元数据(包括签名、函数名、文档字符串等)。这个方案逻辑直观,性能也不错,适合能修改ConversionFunctions模块的场景。
实现步骤:
- 编写一个追踪装饰器,用
functools.wraps包装被装饰函数,确保签名不变 - 用线程局部存储维护调用记录,避免多线程环境下的冲突
- 自动给
ConversionFunctions中的所有函数应用装饰器 - 在转换节点时重置记录、执行函数、提取调用列表
示例代码:
import functools import inspect import threading from typing import Set # 线程局部存储,确保多线程下追踪记录互不干扰 call_tracker = threading.local() call_tracker.called_functions = set() def track_conversion_function(func): @functools.wraps(func) # 关键:完全保留原函数的签名和元数据 def wrapper(*args, **kwargs): # 仅追踪ConversionFunctions模块下的函数 if inspect.getmodule(func).__name__ == 'ConversionFunctions': call_tracker.called_functions.add(func.__name__) return func(*args, **kwargs) return wrapper # 自动给ConversionFunctions中的所有函数添加追踪装饰器 import ConversionFunctions for name, obj in inspect.getmembers(ConversionFunctions, inspect.isfunction): setattr(ConversionFunctions, name, track_conversion_function(obj)) def convert_node(node_info): # 重置当前节点的追踪记录 call_tracker.called_functions.clear() node_conversion_fxn = getattr(ConversionFunctions, node_info['fxn_name']) # 正常做参数签名校验,完全不受装饰器影响 sig = inspect.signature(node_conversion_fxn) try: sig.bind(**node_info['arguments']) except TypeError as e: print(f"参数不匹配:{e}") return node_conversion_fxn(**node_info['arguments']) # 获取当前节点转换过程中调用的所有函数 called_funcs = call_tracker.called_functions.copy() print(f"当前节点调用的函数:{called_funcs}") return called_funcs
方案二:使用sys.settrace无侵入式追踪
如果你无法修改ConversionFunctions模块的代码,可以用Python内置的sys.settrace钩子实现全局追踪。这个方案完全不需要修改原函数,不会影响任何签名,但需要注意在使用后恢复原追踪逻辑,避免干扰其他代码。
实现步骤:
- 定义追踪函数,仅记录
ConversionFunctions模块下的函数调用 - 在转换节点前后开启/关闭追踪,确保只追踪当前节点的转换过程
- 用上下文管理器封装追踪逻辑,让代码更优雅
示例代码:
import sys import inspect from contextlib import contextmanager from typing import Set class ConversionCallTracker: def __init__(self): self.called_functions: Set[str] = set() def _trace_handler(self, frame, event, arg): if event != 'call': return self._trace_handler # 仅追踪ConversionFunctions模块的函数 module_name = frame.f_globals.get('__name__', '') if module_name == 'ConversionFunctions': self.called_functions.add(frame.f_code.co_name) return self._trace_handler @contextmanager def track(self): # 保存原追踪逻辑,避免干扰其他代码 original_trace = sys.gettrace() sys.settrace(self._trace_handler) try: self.called_functions.clear() yield self finally: # 恢复原追踪逻辑 sys.settrace(original_trace) # 初始化追踪器 tracker = ConversionCallTracker() def convert_node(node_info): node_conversion_fxn = getattr(ConversionFunctions, node_info['fxn_name']) # 参数签名校验不受任何影响 sig = inspect.signature(node_conversion_fxn) try: sig.bind(**node_info['arguments']) except TypeError as e: print(f"参数不匹配:{e}") return # 仅在当前节点转换过程中开启追踪 with tracker.track() as t: node_conversion_fxn(**node_info['arguments']) called_funcs = t.called_functions.copy() print(f"当前节点调用的函数:{called_funcs}") return called_funcs
总结建议
- 如果可以修改
ConversionFunctions模块,优先选方案一:装饰器性能更好,逻辑更直观,适合长期做函数使用情况的统计分析。 - 如果不能修改原模块,选方案二:完全无侵入,不会对原代码造成任何改动,上下文管理器的封装也让代码更整洁。
- 两个方案都完整保留了原函数的签名,完全满足你做参数校验的需求,也能准确记录所有嵌套调用的函数,完美匹配你评估函数使用情况的目标。
内容的提问来源于stack exchange,提问作者Liz C.
相关产品推荐
相关产品推荐

