astunparse模块报错:TypeError: 无法用NoneType非整数乘序列
问题分析与解决方案
错误根源
1. 非法的输入代码
你的original_code是一段错误追踪文本,并非合法的Python类代码。ast.parse解析这种非法内容时,会生成结构异常的AST节点,其中可能包含ImportFrom节点但level属性为None(正常情况下level应为整数,0代表绝对导入),这直接导致astunparse在处理时触发乘法错误。
2. AST结构被破坏
created_functions和created_dicts列表中混合了字符串(如函数名、字典名)与合法AST节点。tree.body只能包含ast.stmt类型的节点,混入字符串会彻底破坏AST结构,引发后续解析错误。
3. 遍历AST的方式不安全
使用ast.walk遍历AST时直接修改tree.body,会因为列表长度变化导致索引偏移,可能出现节点处理遗漏或错误。
修正后的代码
import ast import astunparse def transform_code(source_code): tree = ast.parse(source_code) # 仅保存创建的AST节点,无需保存名称字符串 created_functions = [] created_dicts = [] def visit_class(node): nonlocal created_functions, created_dicts class_name = node.name functions = [n for n in node.body if isinstance(n, ast.FunctionDef)] # 为类中每个方法生成独立函数 for func in functions: func_name = f"{class_name}_{func.name}" func_def = ast.FunctionDef( name=func_name, args=func.args, body=func.body, decorator_list=func.decorator_list # 保留原装饰器 ) ast.fix_missing_locations(func_def) created_functions.append(func_def) # 生成类属性对应的字典 dict_name = f"{class_name}_dict" dict_assign = ast.Assign( targets=[ast.Name(id=dict_name, ctx=ast.Store())], value=ast.Call( func=ast.Name(id="create_material", ctx=ast.Load()), args=[], keywords=[], ), ) ast.fix_missing_locations(dict_assign) created_dicts.append(dict_assign) # 生成将方法绑定到字典的代码 update_call = ast.Expr( value=ast.Call( func=ast.Attribute( value=ast.Name(id=dict_name, ctx=ast.Load()), attr="update", ctx=ast.Load(), ), args=[], keywords=[ ast.keyword( arg=func.name, value=ast.Name(id=f"{class_name}_{func.name}", ctx=ast.Load()), ) for func in functions ], ) ) ast.fix_missing_locations(update_call) return [update_call] # 先收集所有ClassDef节点的索引,反向遍历避免索引偏移 class_nodes = [(idx, n) for idx, n in enumerate(tree.body) if isinstance(n, ast.ClassDef)] for idx, node in reversed(class_nodes): tree.body[idx:idx+1] = visit_class(node) # 添加所需导入 imports = [ ast.Import(names=[ast.alias(name="load_image", asname=None)]), ast.Import(names=[ast.alias(name="vec3", asname=None)]), ] # 重组AST body:导入 -> 生成的函数 -> 生成的字典 -> 原代码(替换类后的内容) tree.body = imports + created_functions + created_dicts + tree.body # 转换回源代码 transformed_code = astunparse.unparse(tree) return transformed_code # 合法的测试类代码 original_code = """ class Material: def __init__(self, color): self.color = color def render(self): print(f"Rendering material with color {self.color}") """ transformed_code = transform_code(original_code) print(transformed_code)
关键修正点
- 将
original_code替换为合法的Python类代码,确保ast.parse生成正常的AST结构。 - 移除
created_functions和created_dicts中的字符串元素,仅保留AST节点。 - 改用先收集节点索引再反向遍历的方式处理ClassDef节点,避免索引偏移问题。
- 为所有新创建的AST节点(包括嵌套节点)调用
ast.fix_missing_locations,确保位置信息完整。
内容的提问来源于stack exchange,提问作者FrostDream
相关产品推荐
相关产品推荐

