如何在不执行代码的情况下获取Python函数的完全限定名?
获取Python函数调用的完全限定名:从AST到可行方案
我太懂这种踩坑的感觉了——原本以为用AST遍历就能搞定函数调用的完全限定名,结果真动手才发现,光是处理各种导入别名、作用域覆盖、甚至from xxx import *这种场景,就能把人折腾得头大。毕竟IDE能做“转到定义”,说明这事肯定有解,但自己从零搭AST的处理逻辑确实不现实。下面给你几个实际可行的方案:
1. 直接用成熟的静态分析库(最推荐)
别自己造轮子了!IDE的“转到定义”功能,背后依赖的就是专门做Python静态分析的工具,直接用它们就行:
- 比如jedi:专门为代码补全、定义跳转设计的库,能自动处理绝大多数静态可解析的场景,包括导入别名、局部作用域、嵌套模块等。
举个简单的实现示例:
它甚至能处理import jedi # 你的待分析代码 code = """ from my_module.utils import fib result = fib(100) """ # 初始化jedi脚本对象 script = jedi.Script(code) # 获取所有引用节点 references = script.get_references() for ref in references: # 筛选出函数调用的节点 if ref.type == 'function' and ref.name == 'fib': print(ref.full_name) # 输出:my_module.utils.fibfrom my_module import *这种模糊导入,只要符号能被静态解析,就能给出正确的完全限定名。
2. 用Astroid增强AST分析(进阶)
如果不想引入太重的依赖,或者需要和AST流程结合,可以用astroid——它是pylint的核心静态分析库,对原生AST做了扩展,自带符号表和类型推断能力。
示例代码:
import astroid code = """ import my_module result = my_module.fib(100) """ # 解析代码生成增强版AST树 tree = astroid.parse(code) # 定位到函数调用节点(这里假设第二个语句是调用) call_node = tree.body[1].value try: # 推断函数的定义对象 inferred_objs = call_node.func.infer() for obj in inferred_objs: if isinstance(obj, astroid.FunctionDef): print(obj.qname) # 输出:my_module.fib except astroid.InferenceError: # 处理无法静态推断的情况(比如动态导入、运行时修改的符号) print("无法推断该函数的完全限定名")
Astroid已经帮你处理了很多AST原生没有的符号跟踪逻辑,比自己手写符号表省力太多。
3. 手动基于AST+符号表实现(不推荐)
如果非要自己从零实现,核心是维护一个嵌套的符号表,跟踪每个作用域内的符号来源:
- 遍历AST时,为每个作用域(模块、函数、类)创建一个符号表项,记录符号的映射关系(比如别名对应的原始完全限定名)
- 处理导入语句时,把
import x as y、from x import z as w这类别名和原始路径关联起来 - 遇到函数调用节点(比如你给出的
Call(func=Name(id='fib', ...))时,从当前作用域往上递归查找符号的定义,直到找到对应的完全限定名
但要注意:这种方法只能处理静态可解析的场景,遇到动态导入(比如__import__('foo'))、运行时修改的符号(比如fib = other_func)就无能为力了,而且需要覆盖大量边缘案例,工作量极大。
你给出的函数调用节点示例:
Call(func=Name(id='fib', ctx=Load()), args=[ Num(n=100), ], keywords=[])
这个节点本身只包含fib这个短名,要得到完全限定名,必须结合上下文的导入、作用域信息——这也是原生AST单独做不到的核心原因,必须依赖符号跟踪或者静态分析工具。
内容的提问来源于stack exchange,提问作者hmrdev
相关产品推荐
相关产品推荐

