如何从Python代码自动生成Function Hierarchy Diagrams?
生成Python项目函数层次结构图的可行方案
针对你的需求,这里有几种实用的实现方式,涵盖静态分析和运行时跟踪两种场景:
1. 自定义脚本:结合内置模块 + 绘图库
如果需要完全自定义层次结构的展示逻辑,推荐用Python内置模块做静态分析,再配合绘图库生成可视化图:
方案A:用inspect模块快速实现
inspect可以直接获取模块、类、函数的定义信息,结合pydot(依赖graphviz)生成图:
import inspect import pydot def build_func_hierarchy(module, graph=None): if graph is None: graph = pydot.Dot(graph_type='digraph', rankdir='TB') # 遍历模块内的类和函数 for name, obj in inspect.getmembers(module): # 处理普通函数 if inspect.isfunction(obj) and obj.__module__ == module.__name__: func_node = pydot.Node(f"Func: {name}", shape='box') graph.add_node(func_node) # 简单提取函数内的本地调用(可优化为ast解析) source_lines = inspect.getsource(obj).split('\n') for line in source_lines: stripped = line.strip() if '(' in stripped and ')' in stripped and not stripped.startswith('def'): call_name = stripped.split('(')[0].split('.')[-1] # 检查是否为当前模块内的函数 if call_name in [m[0] for m in inspect.getmembers(module) if inspect.isfunction(m[1])]: graph.add_edge(pydot.Edge(func_node, f"Func: {call_name}")) # 处理类及类方法 elif inspect.isclass(obj) and obj.__module__ == module.__name__: class_node = pydot.Node(f"Class: {name}", shape='oval') graph.add_node(class_node) for method_name, method in inspect.getmembers(obj, inspect.isfunction): if method.__name__ != '__init__' or method.__code__.co_code != b'': method_node = pydot.Node(f"{name}.{method_name}", shape='box') graph.add_node(method_node) graph.add_edge(pydot.Edge(class_node, method_node)) return graph # 使用示例 import your_project_module graph = build_func_hierarchy(your_project_module) graph.write_png('func_hierarchy.png')
注意:这个版本的调用检测比较基础,若要精准识别跨模块调用、排除内置函数等,建议换成ast模块解析源码。
方案B:用ast模块做精准静态分析
ast可以直接解析Python代码的语法树,能更准确提取函数定义、类方法和调用关系:
import ast import pydot class HierarchyVisitor(ast.NodeVisitor): def __init__(self): self.graph = pydot.Dot(graph_type='digraph', rankdir='TB') self.current_scope = None def visit_ClassDef(self, node): class_node = pydot.Node(f"Class: {node.name}", shape='oval') self.graph.add_node(class_node) # 记录当前类作为方法的父节点 prev_scope = self.current_scope self.current_scope = class_node.get_name() # 遍历类内方法 for item in node.body: if isinstance(item, ast.FunctionDef): method_node = pydot.Node(f"{node.name}.{item.name}", shape='box') self.graph.add_node(method_node) self.graph.add_edge(pydot.Edge(class_node, method_node)) self.generic_visit(node) self.current_scope = prev_scope def visit_FunctionDef(self, node): func_node = pydot.Node(f"Func: {node.name}", shape='box') self.graph.add_node(func_node) # 如果在类内,已经处理过关联;如果在模块内,直接添加 if self.current_scope and not self.current_scope.startswith("Class:"): self.graph.add_edge(pydot.Edge(self.current_scope, func_node.get_name())) # 记录当前函数作为内部调用的父节点 prev_scope = self.current_scope self.current_scope = func_node.get_name() self.generic_visit(node) self.current_scope = prev_scope # 使用示例 with open('your_module.py', 'r', encoding='utf-8') as f: code_tree = ast.parse(f.read()) visitor = HierarchyVisitor() visitor.visit(code_tree) visitor.graph.write_svg('func_hierarchy.svg')
2. 用pycallgraph生成运行时函数调用层次
如果需要展示项目运行时的实际函数调用路径,可以用pycallgraph:
- 先安装依赖:
pip install pycallgraph graphviz - 运行你的项目并生成图:
pycallgraph graphviz -- ./your_entry_script.py
运行后会生成一个pycallgraph.png文件,清晰展示函数调用的层次和调用次数,适合排查性能瓶颈或梳理执行流程。
3. Sphinx文档工具生成结构化层次
如果你的项目已经在用Sphinx做文档,可以借助autodoc和graphviz扩展生成结构化的层次图:
- 配置Sphinx的
conf.py,启用扩展:extensions = ['sphinx.ext.autodoc', 'sphinx.ext.graphviz'] - 在rst文档中用
automodule提取模块结构,或者手动编写dot代码生成层次图:.. graphviz:: :align: center digraph { rankdir=TB; "Module: core" -> "Func: init_app"; "Module: core" -> "Class: User"; "Class: User" -> "Method: get_profile"; }
构建文档时会自动生成对应的可视化层次图,适合整合到项目文档中。
内容的提问来源于stack exchange,提问作者Ognjan Ognjanovic
相关产品推荐
相关产品推荐

