如何编写Python Lint规则以阻止全局变量的修改?
自定义Lint规则检测全局变量修改的实现思路
核心逻辑:区分「读取」与「突变」操作
全局变量的读取属于安全操作,但突变操作(包括直接赋值、调用可变类型修改方法、下标/属性修改等)需要被拦截。我们可以通过AST(抽象语法树)遍历实现,分为两个核心步骤:识别模块级全局变量、检测对这些变量的突变行为。
步骤1:收集模块级全局变量
遍历AST的顶层节点,提取所有模块级定义的变量名,无需提前判断类型——后续只需要针对这些变量的操作做检测即可。
示例代码(基于Python原生ast库):
import ast class GlobalVarCollector(ast.NodeVisitor): def __init__(self): self.global_vars = set() def visit_Assign(self, node): # 收集所有模块级赋值的变量名 for target in node.targets: if isinstance(target, ast.Name): self.global_vars.add(target.id) self.generic_visit(node) # 使用方式 with open("your_script.py", "r") as f: tree = ast.parse(f.read()) collector = GlobalVarCollector() collector.visit(tree) global_vars = collector.global_vars
步骤2:检测全局变量的突变操作
遍历函数、类内部等非顶层节点,识别三类会修改全局变量的场景:
- 直接给全局变量赋值(如
GLOBAL_VAR = 10) - 通过下标/属性修改(如
GLOBAL_LIST[0] = 5、GLOBAL_OBJ.attr = "new") - 调用可变类型的修改方法(如
GLOBAL_DICT.update()、GLOBAL_LIST.append())
实现检测的AST访问器:
class GlobalMutationDetector(ast.NodeVisitor): def __init__(self, global_vars): self.global_vars = global_vars self.mutations = [] # 存储检测到的违规信息(行号+描述) def visit_Assign(self, node): # 检测直接赋值、下标修改、属性修改 for target in node.targets: # 场景1:直接给全局变量赋值 if isinstance(target, ast.Name) and target.id in self.global_vars: self.mutations.append( (node.lineno, f"直接修改全局变量 {target.id}") ) # 场景2:通过下标修改全局变量 elif isinstance(target, ast.Subscript): if isinstance(target.value, ast.Name) and target.value.id in self.global_vars: self.mutations.append( (node.lineno, f"通过下标修改全局变量 {target.value.id}") ) # 场景3:通过属性修改全局变量 elif isinstance(target, ast.Attribute): if isinstance(target.value, ast.Name) and target.value.id in self.global_vars: self.mutations.append( (node.lineno, f"通过属性修改全局变量 {target.value.id}") ) self.generic_visit(node) def visit_Call(self, node): # 检测调用全局变量的修改方法 if isinstance(node.func, ast.Attribute): if isinstance(node.func.value, ast.Name) and node.func.value.id in self.global_vars: method_name = node.func.attr # 定义常见的可变类型修改方法集合 modifying_methods = { # 列表方法 "append", "extend", "insert", "remove", "pop", "clear", "sort", "reverse", # 字典方法 "update", "pop", "popitem", "clear", "setdefault", # 集合方法 "add", "update", "remove", "discard", "pop", "clear" } if method_name in modifying_methods: self.mutations.append( (node.lineno, f"调用修改方法 {method_name} 改变全局变量 {node.func.value.id}") ) self.generic_visit(node) # 使用方式 detector = GlobalMutationDetector(global_vars) detector.visit(tree) # 输出检测结果 for lineno, msg in detector.mutations: print(f"Line {lineno}: {msg}")
优化方向:降低误报概率
- 区分可变/不可变类型:如果业务允许重新赋值不可变全局变量(如str、int、tuple),可以通过AST初始化值推导类型,只拦截可变类型的突变。
- 处理
global声明:函数内部用global关键字声明的变量,修改属于明确的全局操作,必须检测;nonlocal变量无需处理。 - 白名单机制:允许特定全局变量被修改,可通过代码注释或配置文件添加白名单规则。
集成到现有Lint工具
如果要将规则集成到pylint这类成熟Lint工具中,可编写自定义插件:
- 继承
pylint.checkers.BaseChecker类 - 重写
visit_assign、visit_call等方法,嵌入上述检测逻辑 - 注册自定义的消息ID、描述和严重级别
内容的提问来源于stack exchange,提问作者sagar_badiyani
相关产品推荐
相关产品推荐

