实现效果
完整代码
from flask import Flask, render_template, request, jsonifyimport sysfrom io import StringIOimport contextlibimport subprocessimport importlibimport threadingimport timeimport astimport re app = Flask(__name__) RESTRICTED_PACKAGES = { 'tkinter': '抱歉,在线编译器不支持 tkinter,因为它需要图形界面环境。请在本地运行需要GUI的代码。', 'tk': '抱歉,在线编译器不支持 tk/tkinter,因为它需要图形界面环境。请在本地运行需要GUI的代码。', 'pygame': 'pygame将被转换为Web版本运行' # 不再限制pygame,而是转换它} def convert_tkinter_to_web(code): """将tkinter代码转换为Web等效实现""" # 解析Python代码 tree = ast.parse(code) # 提取窗口属性 window_props = { 'title': 'Python GUI', 'width': '700', 'height': '500', 'buttons': [], 'labels': [], 'entries': [], 'layout': [] } # 用于存储函数定义 functions = {} # 首先收集所有函数定义 for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): functions[node.name] = ast.unparse(node) # 分析代码中的tkinter组件 for node in ast.walk(tree): if isinstance(node, ast.Assign): if isinstance(node.value, ast.Call): # 提取窗口标题 if hasattr(node.value.func, 'attr') and node.value.func.attr == 'Tk': for subnode in ast.walk(tree): if isinstance(subnode, ast.Call) and hasattr(subnode.func, 'attr'): if subnode.func.attr == 'title' and len(subnode.args) > 0: window_props['title'] = ast.literal_eval(subnode.args[0]) elif subnode.func.attr == 'geometry' and len(subnode.args) > 0: geom = ast.literal_eval(subnode.args[0]) match = re.match(r'(\d+)x(\d+)', geom) if match: window_props['width'] = match.group(1) window_props['height'] = match.group(2) # 提取按钮 elif hasattr(node.value.func, 'attr') and node.value.func.attr == 'Button': button = {'text': 'Button', 'command': None} for kw in node.value.keywords: if kw.arg == 'text': button['text'] = ast.literal_eval(kw.value) elif kw.arg == 'command': # 处理不同类型的command if isinstance(kw.value, ast.Name): # 简单的函数名 button['command'] = kw.value.id elif isinstance(kw.value, ast.Lambda): # Lambda表达式 button['command'] = f"lambda_{len(window_props['buttons'])}" functions[button['command']] = ast.unparse(kw.value) else: # 其他情况,尝试转换为字符串 try: button['command'] = ast.unparse(kw.value) except: button['command'] = 'unknown_command' window_props['buttons'].append(button) # 提取标签 elif hasattr(node.value.func, 'attr') and node.value.func.attr == 'Label': label = {'text': ''} for kw in node.value.keywords: if kw.arg == 'text': try: label['text'] = ast.literal_eval(kw.value) except: # 如果不是字面量,尝试直接转换为字符串 label['text'] = ast.unparse(kw.value) window_props['labels'].append(label) # 提取输入框 elif hasattr(node.value.func, 'attr') and node.value.func.attr == 'Entry': try: entry_id = node.targets[0].id except: entry_id = f"entry_{len(window_props['entries'])}" window_props['entries'].append({'id': entry_id}) # 生成Web等效代码 web_code = f"""<!DOCTYPE html><div class="tk-window" style="width: {window_props['width']}px; height: {window_props['height']}px;"> <div class="tk-title-bar">{window_props['title']}</div> <div class="tk-content">""" # 添加标签 for label in window_props['labels']: web_code += f' <div class="tk-label">{label["text"]}</div>\n' # 添加输入框 for entry in window_props['entries']: web_code += f' <input type="text" class="tk-entry" id="{entry["id"]}">\n' # 添加按钮 for button in window_props['buttons']: command = button['command'] if button['command'] else '' web_code += f' <button class="tk-button" onclick="tkButtonClick(\'{command}\')">{button["text"]}</button>\n' web_code += """ </div></div><script>window.pythonFunctions = {""" # 添加Python函数定义 for func_name, func_code in functions.items(): web_code += f" '{func_name}': {func_code},\n" web_code += """};</script>""" return web_code def convert_pygame_to_web(code): """将pygame代码转换为Web Canvas实现""" web_code = """<canvas id="pygame-canvas" style="border: 1px solid #000;"></canvas><script>const canvas = document.getElementById('pygame-canvas');const ctx = canvas.getContext('2d');// 设置画布大小canvas.width = 800;canvas.height = 600;// 模拟 pygame 的基本功能const pygame = { display: { set_mode: (size) => { canvas.width = size[0]; canvas.height = size[1]; return canvas; }, update: () => { // Canvas 自动更新 }, flip: () => { // Canvas 自动更新 } }, draw: { rect: (surface, color, rect) => { ctx.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`; ctx.fillRect(rect[0], rect[1], rect[2], rect[3]); }, circle: (surface, color, pos, radius) => { ctx.beginPath(); ctx.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`; ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2); ctx.fill(); } }, event: { get: () => [], // 简化的事件处理 pump: () => {} }, init: () => {}, quit: () => {}, time: { Clock: function() { return { tick: (fps) => 1000/fps }; } }};// 转换后的Python代码function runGame() { try { // 这里将插入转换后的游戏代码 %PYTHON_CODE% } catch (error) { console.error('Game error:', error); }}// 启动游戏循环runGame();</script>""" # 处理 Python 代码 try: tree = ast.parse(code) # 转换 Python 代码为 JavaScript js_code = convert_pygame_code_to_js(tree) web_code = web_code.replace('%PYTHON_CODE%', js_code) return web_code except Exception as e: return f"<div class='error'>转换错误: {str(e)}</div>" def convert_pygame_code_to_js(tree): """将 Python AST 转换为 JavaScript 代码""" js_code = [] for node in ast.walk(tree): if isinstance(node, ast.Import): continue # 跳过导入语句 elif isinstance(node, ast.Assign): # 转换赋值语句 if hasattr(node.value, 'func') and isinstance(node.value.func, ast.Attribute): if node.value.func.attr == 'set_mode': js_code.append(f"const screen = pygame.display.set_mode([{node.value.args[0].elts[0].n}, {node.value.args[0].elts[1].n}]);") elif isinstance(node, ast.While): # 转换游戏主循环 js_code.append("function gameLoop() {") # ... 处理循环体 js_code.append(" requestAnimationFrame(gameLoop);") js_code.append("}") js_code.append("gameLoop();") return "\n".join(js_code) def install_package(package): """自动安装缺失的包""" # 检查是否是受限制的包 if package.lower() in RESTRICTED_PACKAGES: raise ImportError(RESTRICTED_PACKAGES[package.lower()]) try: importlib.import_module(package) except ImportError: try: # 尝试使用 pip 安装包 subprocess.check_call([sys.executable, "-m", "pip", "install", package]) except subprocess.CalledProcessError as e: raise Exception(f"安装包 {package} 失败: {str(e)}") def timeout_handler(): """强制终止超时的代码执行""" raise TimeoutError("代码执行超时(最大执行时间:5秒)") @app.route('/')def index(): return render_template('index.html') @app.route('/execute', methods=['POST'])def execute_code(): code = request.json.get('code', '') try: # 检测是否包含pygame代码 if 'pygame' in code: web_code = convert_pygame_to_web(code) return jsonify({ 'status': 'success', 'output': '', 'gui': web_code }) # 检测是否包含tkinter代码 elif 'tkinter' in code or 'tk' in code: web_code = convert_tkinter_to_web(code) return jsonify({ 'status': 'success', 'output': '', 'gui': web_code }) # 非GUI代码正常执行 output_buffer = StringIO() with contextlib.redirect_stdout(output_buffer): exec(code, globals(), {}) output = output_buffer.getvalue() return jsonify({ 'status': 'success', 'output': output if output else '程序执行完成,没有输出' }) except Exception as e: return jsonify({ 'status': 'error', 'output': f'错误: {str(e)}' }) if __name__ == '__main__': app.run(debug=True)