Django在线课程平台后端:如何无序执行学生提交的多模块代码?
问题描述
我正在为在线课程平台开发Django后端,负责运行学生提交的代码。目前的实现要求必须按依赖顺序加载模块(比如调换util_a和util_b的顺序就会报错),但我希望只需要指定主模块,其余模块能像真实文件一样自动处理,不用操心顺序。
现有可运行的代码示例:
import importlib import sys util_a = """ def foo_a(): print('Yes!') """ util_b = """ from util_a import foo_a def foo_b(): foo_a() """ main = """ from util_b import foo_b foo_b() """ def process_module(code, name): module = importlib.util.module_from_spec( importlib.util.spec_from_loader(name, loader=None)) compiled_code = compile(code, '<string>', 'exec') exec(compiled_code, module.__dict__) sys.modules[name] = module for code, name in [(util_a, 'util_a'), (util_b, 'util_b'), (main, 'main')]: process_module(code, name)
备注:我已经实现了用AST分析导入并拓扑排序的复杂方案,现在想找更简便的替代方法。
简便解决方案:自定义导入钩子
利用Python内置的importlib元路径机制,实现自定义的模块查找器和加载器,让Python在导入模块时自动从我们预存的代码中加载,完全不需要手动处理依赖顺序。
修改后的代码如下:
import importlib.abc import importlib.util import sys # 用字典统一存储所有学生提交的模块:键是模块名,值是对应代码,顺序随便放 student_modules = { 'util_b': """ from util_a import foo_a def foo_b(): foo_a() """, 'util_a': """ def foo_a(): print('Yes!') """, 'main': """ from util_b import foo_b foo_b() """ } class StudentModuleLoader(importlib.abc.Loader): def create_module(self, spec): # 创建空的模块对象 return importlib.util.module_from_spec(spec) def exec_module(self, module): # 从字典取出对应代码,编译并执行 code = student_modules[module.__name__] compiled_code = compile(code, '<string>', 'exec') exec(compiled_code, module.__dict__) class StudentModuleFinder(importlib.abc.MetaPathFinder): def find_spec(self, fullname, path, target=None): # 检查要导入的模块是否在我们的学生模块字典里 if fullname in student_modules: # 返回模块规格,指定用我们的加载器来加载 return importlib.util.spec_from_loader( fullname, StudentModuleLoader(), origin='<student-submission>' ) # 不在的话返回None,交给Python默认的导入逻辑处理 return None # 把自定义查找器插到sys.meta_path最前面,确保优先使用我们的逻辑 sys.meta_path.insert(0, StudentModuleFinder()) # 只需要导入主模块就行,所有依赖会自动被加载 import main
为什么这样可行?
Python在导入模块时,会遍历sys.meta_path里的查找器,逐个询问能不能找到要导入的模块。我们的自定义查找器会先检查目标模块是否在student_modules字典里,如果是,就返回对应的模块规格,并指定用我们的加载器来执行模块代码。
当你导入main时:
- Python通过自定义查找器找到
main,用加载器执行它的代码; main里要导入util_b,再次触发查找逻辑,找到并加载util_b;util_b里要导入util_a,同样触发查找,加载util_a;
整个过程完全自动处理依赖顺序,不管student_modules里的模块顺序是什么样的。
优势
- 逻辑简洁:不用写AST解析、拓扑排序的复杂代码,完全复用Python原生的导入机制;
- 兼容性好:学生提交的代码不需要做任何修改,和本地文件导入逻辑完全一致;
- 易扩展:新增模块只需要添加到
student_modules字典即可,不需要修改其他逻辑。
内容的提问来源于stack exchange,提问作者AlwaysLearning
相关产品推荐
相关产品推荐

