🏷️答题页面递归局限的Bug修复!!!

小熊老师
2026-01-06 04:22
239
5 评论
答题页面递归局限的Bug修复!!!

** 代码在本地能跑,但在我的Python世界却不行?一个递归函数的诡异Bug修复记**

作者:小熊老师 | 记录日期:2026年01月07日
💡 标签:#Python #递归 #Django #在线评测系统 #Bug修复


📞 一个紧急来电与崩溃的同学

时间:某日下午3:47
地点:小熊老师的书房
状态:正在和小学妹愉快地微信聊天中

📱 手机突然响起

“小熊老师!救命啊!”电话那头传来一位同学近乎崩溃的声音,“我的代码明明在PyCharm和VS Code上都跑得perfectly fine,但在你的Python世界上就是不行!我检查了八百遍了!”

👨‍🏫 我(淡定地):“同学,冷静一下。肯定是你的代码逻辑有问题,再仔细检查一下。”

时间:某日下午4:17
同一个号码,更加焦虑的语气

“小熊老师!真的不是我的问题!你网站是不是又有new bug了?我又换了几种写法,就是那个递归的写法不行!”

🤔 我(内心os:怎么可能,我的网站可是精心打磨的):“好吧,我现在看看。等一下我先和学妹说一声……”


🔍 问题重现:神秘的递归Bug

📝 题目背景

📖 题目名称:嵌套字典的深度

编写一个函数,接受一个嵌套字典 d,函数结果返回字典的最大嵌套深度。

💻 测试样例

# 样例1
输入d = {'a': 1, 'b': {'c': 2}}
输出: 2

# 样例2  
输入d = {'x': {'y': {'z': 3}}}
输出: 3

# 样例3
输入d = {'flat': 10}
输出: 1

✨ 同学的完美解法(理论上)

这位同学给出了一个非常优雅的递归解法:

def max_dict_depth(d):
    # 如果不是字典或者为空字典,深度为 0
    if not isinstance(d, dict) or not d:
        return 0

    # 初始化最大深度为 1(当前字典本身)
    max_depth = 1

    for value in d.values():
        if isinstance(value, dict):
            # 递归计算子字典深度,并加上当前层
            depth = 1 + max_dict_depth(value)
            # 更新最大深度
            if depth > max_depth:
                max_depth = depth

    return max_depth

🧪 在本地测试: - ✅ PyCharm:通过 - ✅ VS Code:通过
- ✅ Jupyter Notebook:通过 - ❌ Python世界(我的网站):失败!

🎯 替代解法(竟然能通过!)

def nested_dict_depth(d):
    if not isinstance(d, dict):
        return 0

    max_depth = 1
    stack = [(d, 1)]  # 使用栈代替递归

    while stack:
        current_dict, current_depth = stack.pop()

        if current_depth > max_depth:
            max_depth = current_depth

        for value in current_dict.values():
            if isinstance(value, dict):
                stack.append((value, current_depth + 1))

    return max_depth

🤯 诡异的事实: - 迭代解法 ✅ 通过 - 递归解法 ❌ 失败 - 两段代码逻辑完全等价!


🔧 深入调查:代码执行环境的秘密

📊 我的在线评测系统架构

graph TD
    A[用户提交代码] --> B[Django视图层]
    B --> C[代码评估函数]
    C --> D[创建临时执行环境]
    D --> E[exec执行代码]
    E --> F[捕获输出与错误]
    F --> G[验证结果]
    G --> H[返回评分]

🐛 Bug定位过程

第一步:查看错误日志

File "D:\PycharmProjects\Python在线OJ系统进阶版\utils_\main.py", line 91, in evaluate_python_code
    output_result = func(input_data[0])
  File "<string>", line 12, in max_dict_depth
NameError: name 'max_dict_depth' is not defined

🔍 关键发现: NameError: name 'max_dict_depth' is not defined

第二步:分析原评估代码

def evaluate_python_code(code, item):
    # ... 省略部分代码 ...

    # 执行代码块
    exec(code, globals_vars, locals_vars)

    # 获取所有定义的函数
    functions = {name: func for name, func in locals_vars.items() 
                 if callable(func)}

    # 获取第一个函数名
    first_function_name = next((name for name in functions), None)

    if first_function_name:
        # 获取输出结果
        output_result = functions[first_function_name](*input_data)

第三步:发现问题所在!

💡 恍然大悟时刻:

当Python执行 exec(code, globals_vars, locals_vars) 时,函数定义被放入了 locals_vars 字典中。但是,当递归函数 max_dict_depth 尝试调用自己时:

# 在递归调用时
depth = 1 + max_dict_depth(value)  # ← 这里!

Python会在当前作用域中查找 max_dict_depth 这个名称。然而,在 exec() 创建的局部作用域中,递归函数无法看到自己!


🛠️ 解决方案:多层修复策略

方案一:简单的修复(治标不治本)

def evaluate_python_code_fix1(code, case):
    # 创建一个统一的命名空间
    namespace = {}

    # 关键:使用同一个命名空间作为globals和locals
    exec(code, namespace, namespace)

    # 这样函数定义就在全局作用域中了
    functions = [obj for name, obj in namespace.items() 
                 if callable(obj) and not name.startswith('__')]

    if functions:
        func = functions[0]
        result = func(*input_data)  # 现在递归可以正常工作了!

方案二:优雅的完整修复

def evaluate_python_code_fixed(code, case):
    """
    🚀 修复递归函数调用问题的终极版本
    """
    import sys
    import ast
    import traceback

    try:
        # 🛡️ 增加递归深度限制,防止栈溢出
        sys.setrecursionlimit(10000)

        # 📦 解析输入数据
        inputs = []
        if case.input_data:
            for item in case.input_data.strip().split(';'):
                item = item.strip()
                if item:
                    try:
                        inputs.append(ast.literal_eval(item))
                    except:
                        inputs.append(item)

        # 🎯 关键步骤:创建统一的命名空间
        module_ns = {'__builtins__': __builtins__}

        # 💫 执行代码来定义函数(在全局作用域)
        exec(code, module_ns, module_ns)

        # 🔍 查找用户定义的函数
        functions = []
        for name, obj in module_ns.items():
            if (callable(obj) and 
                not name.startswith('__') and 
                not isinstance(obj, type)):
                functions.append((name, obj))

        if not functions:
            return False

        func_name, func = functions[0]

        # 🚀 调用函数(现在递归可以正常工作!)
        try:
            if inputs:
                if len(inputs) > 1:
                    result = func(*inputs)
                else:
                    result = func(inputs[0])
            else:
                result = func()

            # ✅ 验证结果
            expected_str = case.expected_output.strip()

            try:
                expected = ast.literal_eval(expected_str)
                return result == expected
            except:
                return str(result).strip() == expected_str

        except RecursionError:
            print("⚠️ 递归深度超出限制")
            return False

    except Exception as e:
        print(f"❌ 评估异常: {e}")
        traceback.print_exc()
        return False

方案三:防御性编程增强版

def evaluate_python_code_robust(code, case, timeout=5):
    """
    🛡️ 更加健壮的代码评估函数
    """
    import sys
    import ast
    import traceback
    import subprocess
    import tempfile
    import os

    # 创建临时文件执行,彻底隔离环境
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        # 构建完整的测试脚本
        test_script = f'''
import sys
sys.setrecursionlimit(10000)

# 用户代码
{code}

# 测试调用
if __name__ == "__main__":
    try:
        input_data = {repr(case.input_data)}
        # 解析输入...
        result = max_dict_depth(eval(input_data))
        print(result)
    except Exception as e:
        print(f"ERROR: {{e}}", file=sys.stderr)
        sys.exit(1)
        '''
        f.write(test_script)

    # 执行并捕获结果...
    # (完整实现略)

🧪 测试验证

测试1:原来的递归函数

# 用户提交的代码
def max_dict_depth(d):
    if not isinstance(d, dict) or not d:
        return 0

    max_depth = 1

    for value in d.values():
        if isinstance(value, dict):
            depth = 1 + max_dict_depth(value)  # 🎯 递归调用点
            if depth > max_depth:
                max_depth = depth

    return max_depth

# 测试用例
test_input = "{'a': 1, 'b': {'c': 2}}"
expected_output = "2"

修复后结果:通过!

测试2:多层嵌套递归

def deep_recursive(n):
    if n <= 0:
        return 0
    return 1 + deep_recursive(n-1)  # 深度递归

修复后结果:通过!


📚 技术原理深度解析

为什么原来的代码会失败?

# ❌ 错误的方式
exec(code, {}, locals_dict)  
# 函数定义在locals_dict中,递归时找不到自己

# ✅ 正确的方式  
exec(code, unified_namespace, unified_namespace)
# 函数定义在统一的命名空间中,递归时可以看到自己

Python作用域链的奥秘

graph LR
    A[Local Scope<br/>局部作用域] --> B[Enclosing Scope<br/>闭包作用域]
    B --> C[Global Scope<br/>全局作用域]
    C --> D[Built-in Scope<br/>内置作用域]

    E[exec(code, globals, locals)] --> F{locals  globals?}
    F -->|| G[递归函数看不到自己]
    F -->|| H[递归函数正常执行]

🎉 修复成果与反思

🏆 修复成果

  1. ✅ 递归函数现在可以正常工作
  2. ✅ 支持更深的递归调用(通过 sys.setrecursionlimit
  3. ✅ 更好的错误处理和日志记录
  4. ✅ 更安全的代码执行环境

🤔 反思与经验教训

  1. 不要假设用户代码的执行环境:本地IDE和在线环境有很大不同
  2. 递归函数需要特殊照顾:它们对自己的可见性有特殊要求
  3. 防御性编程很重要:总是为最坏情况做准备
  4. 日志是最好的朋友:详细的日志帮助快速定位问题

📈 后续优化计划

TODO_LIST = [
    "✅ 修复递归函数执行问题",
    "🔜 增加代码执行时间限制",
    "🔜 添加内存使用限制", 
    "🔜 实现代码安全性检查",
    "🔜 支持更多编程语言",
    "🔜 优化用户体验界面"
]

💬 与同学的后续对话

时间:修复后的第二天

👨‍🎓 同学:“小熊老师,我的代码现在通过了!你是怎么做到的?”

👨‍🏫 我:“其实就是一个作用域的小问题。你的递归函数在执行时看不到自己,就像……”

👨‍🎓 同学:“就像照镜子时镜子里的我看不到镜子外的我?”

👨‍🏫 我:“……这个比喻还挺贴切!总之现在已经修复了,以后递归函数都可以正常工作了!”

👨‍🎓 同学:“太好了!那我继续去刷题了,顺便……小学妹微信推我一下?”

👨‍🏫 我:“😅 这个……我们还是讨论Python吧!”


✨ 结语

这次Bug修复之旅让我深刻体会到:在线评测系统不仅是评判者,更是代码的执行环境提供者。我们需要确保这个环境既安全又功能完整,能够正确处理各种编程范式,包括优雅的递归算法。

🌱 给所有编程学习者的建议: - 遇到问题不要轻易放弃,可能是环境问题而非代码问题 - 理解代码执行环境的重要性 - 递归是一种强大的工具,但要知道它的局限性 - 调试时,尝试不同的实现方式可以帮助定位问题

🔧 给开发者同行的建议: - 总是考虑边缘情况 - 详细记录和日志是你的最佳伙伴 - 用户反馈是改进系统的最好动力

最后,感谢这位细心的同学发现了这个隐藏的Bug!🎯 正是这样的反馈让我们的平台变得更好。


🐻 小熊老师的承诺: 我会继续完善这个Python学习平台,让它成为大家学习编程的乐园!遇到任何问题,随时来找我~ 💖


✨ 在编程的世界里,每一个Bug都是一次学习的机会,每一次修复都是一次成长。保持好奇,保持热爱,代码之路,我们一起前行! 🚀


发表评论

登录后发表评论

登录后你可以点赞、回复其他评论


返回博客列表
标签: 网站动态