Python包内globals()失效:如何让函数将对象加入全局工作区?
问题原因
当source_lines函数放入包模块后,函数内部调用的globals()返回的是包模块自身的全局命名空间,而非调用该函数的脚本/交互式环境的全局命名空间。因此exec执行的代码会将对象注入到包模块的globals中,调用者的全局空间自然无法访问这些对象。
解决方案
要实现将对象注入到调用者的全局空间,需要获取调用者的globals,而非函数所在模块的globals。可以通过inspect模块获取调用栈的上一层上下文,直接拿到调用者的全局命名空间。
修改后的函数代码
from re import search import tempfile import os import inspect def source_lines(path: str, start_line: str, end_line: str): # 获取调用者的全局命名空间 caller_globals = inspect.stack()[1].frame.f_globals with open(path, "r") as f: script = f.readlines() starts = [i for i, line in enumerate(script) if search(start_line, line)] ends = [i for i, line in enumerate(script) if search(end_line, line)] out = [] for s, e in zip(starts, ends): out.append(script[s:e]) out_flat = [item for sublist in out for item in sublist] with tempfile.TemporaryDirectory() as td: f_name = os.path.join(td, 'tempsource.py') with open(f_name, 'w+t') as fh: fh.write('\n'.join(out_flat)) fh.seek(0) # 用调用者的globals执行代码 exec(open(f_name).read(), caller_globals)
关键改动说明
- 引入
inspect模块,通过inspect.stack()[1].frame.f_globals获取调用者的全局命名空间:inspect.stack()返回调用栈列表,索引1对应调用source_lines的上下文层级,从中取出frame的f_globals就是调用者的全局字典。 - 将
exec的第二个参数从globals()替换为caller_globals,确保执行的代码将对象注入到调用者的全局空间。
备选方案:手动传入globals
如果不想依赖inspect模块,可以让函数接收一个globals_dict参数,由用户调用时主动传入自身的globals():
def source_lines(path: str, start_line: str, end_line: str, globals_dict=None): # 若未传参,默认使用调用者的globals(需用户手动传入才生效) if globals_dict is None: raise ValueError("请传入调用者的globals()作为globals_dict参数") # ... 其余逻辑不变 ... exec(open(f_name).read(), globals_dict) # 用户调用示例 source_lines( path='delete_me.py', start_line='^## (Dependencies|Main fun)', end_line='^#\s*$', globals_dict=globals() )
这种方式需要用户主动传参,自动化程度不如inspect方案,但避免了对inspect模块的依赖。
注意事项
inspect.stack()在性能敏感场景下存在轻微开销,但完全适配交互式开发的使用场景。- 你提到
exec存在代码异味,这一观点是合理的:动态执行代码会降低代码可维护性与安全性,建议仅在交互式开发的特定场景下使用,生产环境尽量避免此类实现。
内容的提问来源于stack exchange,提问作者Tyler Rinker
相关产品推荐
相关产品推荐

