** 代码在本地能跑,但在我的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[递归函数正常执行]
🎉 修复成果与反思
🏆 修复成果
- ✅ 递归函数现在可以正常工作
- ✅ 支持更深的递归调用(通过
sys.setrecursionlimit) - ✅ 更好的错误处理和日志记录
- ✅ 更安全的代码执行环境
🤔 反思与经验教训
- 不要假设用户代码的执行环境:本地IDE和在线环境有很大不同
- 递归函数需要特殊照顾:它们对自己的可见性有特殊要求
- 防御性编程很重要:总是为最坏情况做准备
- 日志是最好的朋友:详细的日志帮助快速定位问题
📈 后续优化计划
TODO_LIST = [
"✅ 修复递归函数执行问题",
"🔜 增加代码执行时间限制",
"🔜 添加内存使用限制",
"🔜 实现代码安全性检查",
"🔜 支持更多编程语言",
"🔜 优化用户体验界面"
]
💬 与同学的后续对话
时间:修复后的第二天
👨🎓 同学:“小熊老师,我的代码现在通过了!你是怎么做到的?”
👨🏫 我:“其实就是一个作用域的小问题。你的递归函数在执行时看不到自己,就像……”
👨🎓 同学:“就像照镜子时镜子里的我看不到镜子外的我?”
👨🏫 我:“……这个比喻还挺贴切!总之现在已经修复了,以后递归函数都可以正常工作了!”
👨🎓 同学:“太好了!那我继续去刷题了,顺便……小学妹微信推我一下?”
👨🏫 我:“😅 这个……我们还是讨论Python吧!”
✨ 结语
这次Bug修复之旅让我深刻体会到:在线评测系统不仅是评判者,更是代码的执行环境提供者。我们需要确保这个环境既安全又功能完整,能够正确处理各种编程范式,包括优雅的递归算法。
🌱 给所有编程学习者的建议: - 遇到问题不要轻易放弃,可能是环境问题而非代码问题 - 理解代码执行环境的重要性 - 递归是一种强大的工具,但要知道它的局限性 - 调试时,尝试不同的实现方式可以帮助定位问题
🔧 给开发者同行的建议: - 总是考虑边缘情况 - 详细记录和日志是你的最佳伙伴 - 用户反馈是改进系统的最好动力
最后,感谢这位细心的同学发现了这个隐藏的Bug!🎯 正是这样的反馈让我们的平台变得更好。
🐻 小熊老师的承诺: 我会继续完善这个Python学习平台,让它成为大家学习编程的乐园!遇到任何问题,随时来找我~ 💖
✨ 在编程的世界里,每一个Bug都是一次学习的机会,每一次修复都是一次成长。保持好奇,保持热爱,代码之路,我们一起前行! 🚀
请登录后发表评论
登录后你可以点赞、回复其他评论