You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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时:

  1. Python通过自定义查找器找到main,用加载器执行它的代码;
  2. main里要导入util_b,再次触发查找逻辑,找到并加载util_b;
  3. util_b里要导入util_a,同样触发查找,加载util_a;
    整个过程完全自动处理依赖顺序,不管student_modules里的模块顺序是什么样的。

优势

  • 逻辑简洁:不用写AST解析、拓扑排序的复杂代码,完全复用Python原生的导入机制;
  • 兼容性好:学生提交的代码不需要做任何修改,和本地文件导入逻辑完全一致;
  • 易扩展:新增模块只需要添加到student_modules字典即可,不需要修改其他逻辑。

内容的提问来源于stack exchange,提问作者AlwaysLearning

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 08:23:10