Python中如何从内部函数对象获取其父函数的层级路径信息?
问题背景与需求
假设我们在my_module.py中定义如下函数:
from typing import Callable def make_function(scalar: float) -> Callable[[float], float]: def increment(x: float) -> float: return x + scalar return increment
运行时获取内部函数对象时,其__repr__会显示包含父函数的信息:
>>> make_function(2) <function my_module.make_function.<locals>.increment(x: float) -> float>
需求:有没有优雅的方式从该内部函数对象获取其父函数的相关信息?尤其需要动态生成类似my_module.make_function.increment格式的字符串,且支持任意嵌套层级(如my_module.outer_func_1.outer_func_2.inner_func)。
已尝试的方法:
inspect模块:inspect.getmodule(func).__name__仅返回模块路径,无法获取父函数链- 函数的
__code__.co_*系列属性:未找到指向父函数的直接途径 - 操作
func.__globals__:需要遍历全局命名空间匹配,复杂度高且容易出错
理想方案:无需装饰或修改原函数,仅通过检查函数对象本身实现。
解决思路
方案1:利用__qualname__属性(Python 3.3+ 最优方案)
从Python 3.3开始,函数对象新增了__qualname__属性,它会直接返回函数的嵌套限定名,天然包含父函数层级信息,结合模块名即可生成完整路径:
import inspect def get_full_nested_func_name(func): module_name = inspect.getmodule(func).__name__ return f"{module_name}.{func.__qualname__}"
测试示例:
from my_module import make_function inc_func = make_function(2) print(inc_func.__qualname__) # 输出: make_function.increment print(get_full_nested_func_name(inc_func)) # 输出: my_module.make_function.increment
这是最优雅可靠的方案,__qualname__是Python标准定义的属性,专门用于表示嵌套函数/类的限定名称,完全适配任意嵌套层级的需求。
方案2:解析__repr__字符串(简单直接,依赖CPython实现)
如果需要兼容Python 3.3之前的版本,可以通过正则解析函数__repr__的输出内容,提取嵌套路径:
import re def get_nested_func_name(func): repr_str = repr(func) match = re.search(r'<function (\S+)\(', repr_str) if not match: return func.__name__ full_path = match.group(1) return full_path.replace('.<locals>', '')
测试示例:
from my_module import make_function inc_func = make_function(2) print(get_nested_func_name(inc_func)) # 输出: my_module.make_function.increment
注意:该方法依赖CPython的__repr__输出格式,其他Python实现(如PyPy)的输出可能不同,存在兼容性风险。
方案3:通过闭包回溯父函数(兼容旧版本,逻辑更严谨)
内部函数会通过闭包引用外部函数的变量,可借助inspect模块分析闭包信息,递归回溯父函数链:
import inspect def get_parent_func(func): # 从闭包变量中查找父函数引用 closure_vars = inspect.getclosurevars(func) for cell in closure_vars.nonlocals.values(): if inspect.isfunction(cell): return cell # 尝试从自由变量对应的全局对象中查找 for var_name in func.__code__.co_freevars: if var_name in func.__globals__: obj = func.__globals__[var_name] if inspect.isfunction(obj): return obj return None def build_full_func_path(func): path_parts = [func.__name__] current_func = func while True: parent = get_parent_func(current_func) if not parent: break path_parts.insert(0, parent.__name__) current_func = parent # 拼接模块名 module = inspect.getmodule(func) if module: path_parts.insert(0, module.__name__) return '.'.join(path_parts)
测试示例:
from my_module import make_function inc_func = make_function(2) print(build_full_func_path(inc_func)) # 输出: my_module.make_function.increment
说明:该方法兼容性更好,但如果内部函数没有引用父函数的变量(无闭包),则无法回溯到父函数,属于边界情况需额外处理。
内容的提问来源于stack exchange,提问作者Azmy Rajab
相关产品推荐
相关产品推荐

