使用Python AST模块解析fx.bind()中作为参数的可调用对象
解析fx.bind()调用并构建函数-快捷键映射字典
核心思路
AST是静态语法分析工具,要拿到实际的函数对象必须结合模块的运行时环境——先通过AST定位fx.bind()调用并提取参数信息,再加载目标模块获取对应函数实例。
具体实现步骤
1. 识别fx.bind()调用并提取关键信息
在visit_Call方法中,先判断当前Call节点是否为fx.bind()调用,再提取快捷键(第一个参数)和可调用对象的名称标识:
import ast from typing import Dict, Optional, Tuple class BindVisitor(ast.NodeVisitor): def __init__(self): self.bind_calls: list[Tuple[str, str]] = [] # 存储(快捷键, 函数标识) def visit_Call(self, node: ast.Call): # 判断是否是fx.bind调用 if (isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'fx' and node.func.attr == 'bind' and len(node.args) >= 2): # 提取快捷键:兼容Python3.8+的Constant和旧版Str节点 key_arg = node.args[0] if isinstance(key_arg, ast.Constant) and isinstance(key_arg.value, str): hotkey = key_arg.value elif isinstance(key_arg, ast.Str): # Python3.7及以下 hotkey = key_arg.s else: hotkey = None # 非字符串参数直接跳过 # 递归提取可调用对象的全限定名 func_identifier = self._get_func_identifier(node.args[1]) if hotkey and func_identifier: self.bind_calls.append((hotkey, func_identifier)) # 继续遍历子节点 self.generic_visit(node) def _get_func_identifier(self, node: ast.AST) -> Optional[str]: if isinstance(node, ast.Name): return node.id elif isinstance(node, ast.Attribute): parent_id = self._get_func_identifier(node.value) if parent_id: return f"{parent_id}.{node.attr}" # 暂不支持lambda、表达式等无明确名称的可调用对象,返回None return None
2. 加载模块并获取实际函数对象
拿到bind_calls中的函数标识后,加载目标模块并从运行时环境中提取对应函数实例:
import importlib.util import os def load_module_from_path(module_path: str): # 从文件路径加载模块 spec = importlib.util.spec_from_file_location("target_module", module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def get_function_from_identifier(module, identifier: str): # 根据全限定名从模块中获取函数对象 parts = identifier.split('.') current_obj = module for part in parts: try: current_obj = getattr(current_obj, part) except AttributeError: return None # 验证是否为可调用对象 if callable(current_obj): return current_obj return None def build_hotkey_map(module_paths: list[str]) -> Dict[callable, str]: hotkey_map = {} for path in module_paths: if not path.endswith('.py'): continue # 解析AST with open(path, 'r', encoding='utf-8') as f: tree = ast.parse(f.read()) visitor = BindVisitor() visitor.visit(tree) # 加载模块 module = load_module_from_path(path) # 构建映射字典 for hotkey, func_id in visitor.bind_calls: func = get_function_from_identifier(module, func_id) if func: hotkey_map[func] = hotkey return hotkey_map
3. 注意事项
- 局部函数处理:如果
fx.bind()绑定的是模块内的局部函数(如函数内部定义的嵌套函数),上述方法无法直接获取,需额外遍历函数内部AST并结合函数执行后的局部作用域处理。 - Lambda表达式:lambda无明确名称标识,
_get_func_identifier会返回None。若需支持,可提取其代码字符串作为标识,但无法对应到唯一函数对象。 - 模块执行风险:加载并执行模块会触发模块级初始化代码,若目标模块有副作用(如文件写入、网络请求),需谨慎操作。
内容的提问来源于stack exchange,提问作者Corin
相关产品推荐
相关产品推荐

