Python3.7中exec()函数在类/函数内失效的原因及解决方案咨询
问题分析:exec()在Python类方法与全局代码中的行为差异
问题重现
你碰到的这个情况很典型:exec()在全局代码里能正常给变量赋值,但放到类的方法内部时,逻辑完全一致却没法正确更新变量。先把你的代码和配套数据整理清楚:
代码示例
#!/usr/bin/env python import pandas as pd ################################# exec() WITHIN A CLASS ################################### class Test: def __init__(self): pass def f(self): pose, frame, min_complexE, min_lEfree, best_structvar, best_conf, complexE, ligandE_bound, proteinE_bound, \ min_lEfree = [None] * 10 table = pd.read_table("sample_scores.txt", delim_whitespace=True, skiprows=[0]) # 1st line is a comment table_columns = table.columns for i, row in table.iterrows(): # Variable assignemnt for varname in ["pose", "frame", "min_complexE", "min_lEfree", "best_structvar", "best_conf", "complexE", "ligandE_bound", "proteinE_bound", "min_lEfree", "Eint"]: if varname in table_columns: exec("%s = %s" % (varname, row[varname]), globals(), globals()) else: exec("%s = None" % (varname), globals(), globals()) print("proteinE_bound within class =", proteinE_bound) def caller1(self): self.f() def caller2(self): self.caller1() Test().caller2() ################################# exec() IN RAW CODE ################################### pose, frame, min_complexE, min_lEfree, best_structvar, best_conf, complexE, ligandE_bound, proteinE_bound, \ min_lEfree = [None] * 10 table = pd.read_table("sample_scores.txt", delim_whitespace=True, skiprows=[0]) # 1st line is a comment table_columns = table.columns for i, row in table.iterrows(): # Variable assignemnt for varname in ["pose", "frame", "min_complexE", "min_lEfree", "best_structvar", "best_conf", "complexE", "ligandE_bound", "proteinE_bound", "min_lEfree", "Eint"]: if varname in table_columns: exec("%s = %s" % (varname, row[varname]), globals(), globals()) else: exec("%s = None" % (varname), globals(), globals()) print("proteinE_bound as raw code =", proteinE_bound)
配套数据文件 sample_scores.txt
# Contains all results. For the best result for each compound please refer to file BEST_RESULTS. molname Eint complexE ligandE_bound proteinE_bound stereoisomer ionstate tautomer pose frame LEM00001847 -63.000496 -17406.593934 -84.868633 -17258.724804 1 1 1 1 571 LEM00001847 -62.412897 -17474.918135 -64.778724 -17347.726515 1 1 1 1 171 LEM00001847 -61.249384 -17423.452346 -82.875735 -17279.327226 1 1 1 1 531
原因分析
核心问题出在Python的作用域规则和exec()的参数使用上,我之前踩过这个坑:
作用域不匹配:
- 全局代码里,你定义的
proteinE_bound等变量属于全局作用域,调用exec(..., globals(), globals())时,明确指定了全局命名空间作为执行环境,赋值直接修改全局变量,后续打印自然能拿到更新后的值。 - 但在类方法
f()内部,你一开始定义的proteinE_bound是局部变量(属于方法的局部作用域),可你却让exec()把值写到全局命名空间里。方法内部的局部变量和全局变量是完全独立的两个变量——你以为在更新局部的proteinE_bound,实际上是在全局作用域创建/更新了另一个同名变量,而方法里的局部变量还是初始的None,所以打印时输出的是局部的那个None。
- 全局代码里,你定义的
Python局部作用域的限制:
Python编译函数/方法时,会扫描所有变量赋值语句,把被赋值的变量标记为局部变量。exec()的代码是运行时动态执行的,编译阶段Python不知道你要给哪些局部变量赋值,所以即使你想通过exec()修改局部变量,也绕不开这个机制。
解决方案
针对这个场景,有几种更合理的替代方案,比强行用exec()修改局部变量更安全:
方案1:使用字典存储变量(推荐)
放弃直接创建多个局部变量,改用一个字典来存储所有需要的字段,不管是类方法还是全局代码里都能正常工作:
import pandas as pd class Test: def __init__(self): pass def f(self): # 用字典统一管理变量,替代多个独立变量 vars_dict = { "pose": None, "frame": None, "min_complexE": None, "min_lEfree": None, "best_structvar": None, "best_conf": None, "complexE": None, "ligandE_bound": None, "proteinE_bound": None, "Eint": None } table = pd.read_table("sample_scores.txt", delim_whitespace=True, skiprows=[0]) table_columns = table.columns for _, row in table.iterrows(): for varname in vars_dict.keys(): vars_dict[varname] = row[varname] if varname in table_columns else None print("proteinE_bound within class =", vars_dict["proteinE_bound"]) def caller1(self): self.f() def caller2(self): self.caller1() Test().caller2() # 全局代码版本同样适用 vars_dict = { "pose": None, "frame": None, "min_complexE": None, "min_lEfree": None, "best_structvar": None, "best_conf": None, "complexE": None, "ligandE_bound": None, "proteinE_bound": None, "Eint": None } table = pd.read_table("sample_scores.txt", delim_whitespace=True, skiprows=[0]) table_columns = table.columns for _, row in table.iterrows(): for varname in vars_dict.keys(): vars_dict[varname] = row[varname] if varname in table_columns else None print("proteinE_bound as raw code =", vars_dict["proteinE_bound"])
方案2:如果一定要用exec(),修改局部作用域(不推荐)
如果你坚持要用exec(),可以通过locals()获取局部作用域的字典,但要注意:Python官方不推荐依赖locals()的修改,因为函数内部locals()的行为可能不稳定。修改后的类方法代码如下:
def f(self): pose, frame, min_complexE, min_lEfree, best_structvar, best_conf, complexE, ligandE_bound, proteinE_bound, \ min_lEfree = [None] * 10 table = pd.read_table("sample_scores.txt", delim_whitespace=True, skiprows=[0]) table_columns = table.columns local_vars = locals() # 获取当前局部作用域的字典 for i, row in table.iterrows(): for varname in ["pose", "frame", "min_complexE", "min_lEfree", "best_structvar", "best_conf", "complexE", "ligandE_bound", "proteinE_bound", "min_lEfree", "Eint"]: if varname in table_columns: exec("%s = %s" % (varname, row[varname]), globals(), local_vars) else: exec("%s = None" % (varname), globals(), local_vars) # 必须从local_vars里取变量,因为局部变量可能没被同步 print("proteinE_bound within class =", local_vars["proteinE_bound"])
方案3:利用pandas的行转属性(更简洁)
既然你在用pandas,直接把行数据转换成对象属性会更简洁:
import pandas as pd from types import SimpleNamespace class Test: def __init__(self): pass def f(self): table = pd.read_table("sample_scores.txt", delim_whitespace=True, skiprows=[0]) # 把行数据转换成可通过属性访问的对象 for _, row in table.iterrows(): data = SimpleNamespace(**row.to_dict()) # 补充缺失的字段 required_vars = ["pose", "frame", "min_complexE", "min_lEfree", "best_structvar", "best_conf", "Eint"] for var in required_vars: if not hasattr(data, var): setattr(data, var, None) print("proteinE_bound within class =", data.proteinE_bound) def caller1(self): self.f() def caller2(self): self.caller1() Test().caller2()
总结
尽量避免在函数/方法内部用exec()修改局部变量,这不仅容易踩作用域的坑,代码可读性也差。用字典或者SimpleNamespace来管理动态变量是更稳妥的选择。
内容的提问来源于stack exchange,提问作者tevang
相关产品推荐
相关产品推荐

