支持 input 函数的在线 python 运行环境 - 基于队列

支持 input 函数的在线 python 运行环境 - 基于队列

    • 思路
      • 两次用户输入
      • 三次用户输入
    • 实现
      • 前端使用 vue + element ui
      • Windows 环境的执行器
      • 子进程需要执行的代码
    • 代码仓库
    • 参考

本文提供了一种方式来实现支持 input 函数,即支持用户输的在线 python 运行环境。效果如下图所示:

image-20240104163231417

image-20240104163319674
在这里插入图片描述

思路

  • 前端使用一个数组 input_queue 记录用户从点击运行按钮到现在的所有输入

  • 点击运行按钮时将 codeinput_queue 传给后端

  • 后端将参数传给执行 python 代码的子进程

  • 子进程重写 input() 函数,假设新的实现为 input_wrapper,代码如下,到用户代码运行到 input() 函数时,会执行重写的 input_wrapper(), 在 input_wrapper 中获取到 input_queue,然后使用 input_queue.pop(0) 弹出用户输入最早的信息,如果 input_queue 为空,则说明需要用户输入,通过抛出 InputRequestException 异常的方式通知后端

    def input_wrapper(prompt=''):if input_queue:input_str = input_queue.pop(0)sys.stdout.write(str(prompt) + input_str + "\n")return input_strraise InputRequestException(str(prompt))
    
  • 后端通过处理子进程的标准输出、标准错误,知晓需要用户输入,然后向前端返回以下 json,event.type 为 input_request 代表需要用户输入,prompt 是提示信息

    {"is_timeout": false,"done": false,"event": {"type": "input_request","prompt": "请输入姓名:"}
    }
    
  • 前端弹出弹框提示用户输入,用户输入并点击继续执行按钮时,会将本次的输入追加到 input_queue 的末尾,然后再次调用运行接口,这样循环往复直到程序结束

    image-20240104170040751

在执行以下代码时,可能需要两次用户输入,也可能需要三次。

name = input("请输入姓名:")
print("姓名:", name)if name == "tom":age = input("请输入年龄:")print("年龄:", age)gender = input("请输入性别:")
print("性别:", gender)

两次用户输入

点击运行按钮

请求参数中的 input_queue 为 []{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": []
}返回值
{"is_timeout": false,"done": false,"event": {"type": "input_request","prompt": "请输入姓名:"}
}

输入 jack

请求参数中的 input_queue 为 ["jack"]{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": ["jack"]
}返回值
{"is_timeout": false,"done": false,"event": {"type": "input_request","prompt": "请输入性别:"}
}

输入 男

请求参数中的 input_queue 为 ["jack", "男"]
{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": ["jack","男"]
}返回值
{"is_timeout": false,"done": true,"output": "请输入姓名:jack\r\n姓名: jack\r\n请输入性别:男\r\n性别: 男\r\n"
}

三次用户输入

点击运行按钮

请求参数中的 input_queue 为 []
{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": []
}返回值
{"is_timeout": false,"done": false,"event": {"type": "input_request","prompt": "请输入姓名:"}
}

输入 tom

请求参数中的 input_queue 为 ["tom"]
{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": ["tom"]
}返回值
{"is_timeout": false,"done": false,"event": {"type": "input_request","prompt": "请输入年龄:"}
}

输入 18

请求参数中的 input_queue 为 ["tom", "18"]
{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": ["tom","18"]
}返回值
{"is_timeout": false,"done": false,"event": {"type": "input_request","prompt": "请输入性别:"}
}

输入 男

请求参数中的 input_queue 为 ["tom", "18", "男"]
{"code": "name = input(\"请输入姓名:\")\nprint(\"姓名:\", name)\n\nif name == \"tom\":\n    age = input(\"请输入年龄:\")\n    print(\"年龄:\", age)\n\ngender = input(\"请输入性别:\")\nprint(\"性别:\", gender)","input_queue": ["tom","18","男"]
}返回值
{"is_timeout": false,"done": true,"output": "请输入姓名:tom\r\n姓名: tom\r\n请输入年龄:18\r\n年龄: 18\r\n请输入性别:男\r\n性别: 男\r\n"
}

实现

前端使用 vue + element ui

<!DOCTYPE html>
<html lang="" style="height: 100%;">
<head><meta charset="UTF-8"><link rel="stylesheet" href="./element-ui/index.css"><title>在线 python 执行</title>
</head>
<body style="height: 100%;margin: 0;">
<div id="app" style="height: 98%;width: 98%;padding: 5px"><el-inputtype="textarea":autosize="{ minRows: 10, maxRows: 100}"placeholder="请输入代码"v-model="code"></el-input><el-button type="primary" style="margin-top: 5px;margin-bottom: 5px" @click="exec()">运行</el-button><el-inputtype="textarea":autosize="{ minRows: 10, maxRows: 100}"placeholder="运行结果"v-model="result"></el-input>
</div>
</body>
<script src="./axios.min.js"></script>
<script src="./vue.js"></script>
<script src="./element-ui/index.js"></script>
<script>new Vue({el: '#app',mounted() {},methods: {exec() {const params = {code: this.code,input_queue: this.input_queue}axios.post('http://localhost:8080/exec', params).then(res => {console.log("exec", res)if (res.data.done) {// 执行结束了,需要清空队列this.clearQueue()if (res.data.is_timeout) {// 执行超时this.$message("执行超时");} else {// 正常执行结束this.result = res.data.output}} else {// 执行中,需要用户输入const event = res.data.eventif (event.type === 'input_request') {// 弹框提示用户输入this.$prompt(event.prompt, '输入', {confirmButtonText: '继续执行',cancelButtonText: '终止执行',showClose: false,closeOnClickModal: false,closeOnPressEscape: false}).then(({value}) => {// 继续执行,将本次输入的信息追加进队列,然后再次执行this.input_queue.push(value)this.exec()}).catch((action) => {// 终止执行,需要清空队列console.log("action ", action)this.clearQueue()this.$message("终止执行")});}}})},clearQueue() {this.input_queue = []}},data() {return {code:
`name = input("请输入姓名:")
print("姓名:", name)if name == "tom":age = input("请输入年龄:")print("年龄:", age)gender = input("请输入性别:")
print("性别:", gender)
`,input_queue: [],result: null,}}})</script>
</html>

Windows 环境的执行器

import json
import os
import subprocess
import threading
from threading import Timerimport psutilclass AbstractExecutor:def __init__(self, param):# param 包括 code、input_queueself.param = param# 用于保护 is_timeout 的锁self.lock = threading.Lock()# 是否执行超时了self.is_timeout = Nonedef timeout_callback(self, p: subprocess.Popen):"""执行超时时的回调,会终止执行 python 代码的进程组:param p: 执行 python 代码的进程"""with self.lock:if self.is_timeout is None:self.is_timeout = Trueif self.is_timeout:try:# 终止执行 python 代码的进程组self.terminating_process_group(p)except Exception as e:print("超时回调异常, error: %s", e)def terminating_process_group(self, p: subprocess.Popen):"""终止进程 p 及其子进程:param p: 要终止的进程"""raise NotImplementedError()def create_popen(self) -> subprocess.Popen:"""创建 subprocess.Popen,必须将 stderr 重定向到 stdout"""raise NotImplementedError()def output(self, stdout):if stdout is not None:return stdout.decode("utf-8")else:return ""def execute(self):p = self.create_popen()timer = Timer(3, self.timeout_callback, [p])timer.start()try:# 从标准输入传入 json 参数:code、input_queuep.stdin.write(json.dumps(self.param).encode(encoding="utf-8"))stdout, stderr = p.communicate()with self.lock:if self.is_timeout is None:self.is_timeout = Falsefinally:timer.cancel()return self.is_timeout, self.output(stdout)class WindowsExecutor(AbstractExecutor):__output_prefix = "Active code page: 65001\r\n"def create_popen(self) -> subprocess.Popen:filename = r"D:\project\python\online-python-code-executor\queue-base\exec_py.py"cmd = 'chcp 65001 & set PYTHONIOENCODING=utf-8 & python ' + filename# 将 stderr 重定向到了 stdoutreturn subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,shell=True)def terminating_process_group(self, p: subprocess.Popen):proc_pid = p.pidparent_proc = psutil.Process(proc_pid)for child_proc in parent_proc.children(recursive=True):print(child_proc.pid)child_proc.kill()parent_proc.kill()print(parent_proc.pid)def output(self, stdout):output = super().output(stdout)if output.startswith(self.__output_prefix):return output.removeprefix(self.__output_prefix)else:return outputif os.name == "nt":executor_cls = WindowsExecutordef execute(param):# 执行用户代码is_timeout, stdout = executor_cls(param).execute()if is_timeout:# 执行超时了return {"is_timeout": is_timeout,"done": True,"output": stdout,}else:arr = stdout.split("InputRequestException")if len(arr) > 1:# 需要用户输入return {"is_timeout": is_timeout,"done": False,"event": {"type": "input_request","prompt": arr[-1]}}else:# 正常执行结束return {"is_timeout": is_timeout,"done": True,"output": stdout,}

子进程需要执行的代码

import json
import sysinput_queue = []class InputRequestException(Exception):"""抛出此异常表示需要用户输入"""passdef execute(param):# 重写 input 函数__builtins__.input = input_wrapper# input_queueglobal input_queueinput_queue = param["input_queue"]try:# 执行代码exec(param["code"])except InputRequestException as e:# 如果需要用户输入,则直接退出sys.stdout.write("\n" + "InputRequestException" + e.args[0])exit()def input_wrapper(prompt=''):# 从 input_queue 中弹出if input_queue:input_str = input_queue.pop(0)sys.stdout.write(str(prompt) + input_str + "\n")return input_str# 需要用户输入raise InputRequestException(str(prompt))if __name__ == '__main__':# 从标准输入读取 json 参数:code、input_queuearg = sys.stdin.read()# 执行execute(json.loads(arg))

代码仓库

  • online-python-code-executor/queue-base (github.com)

参考

  • https://pythontutor.com
  • https://github.com/seamile/PyTutor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/325194.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5.1_UMG序列帧动画制作

UE5.1_UMG序列帧动画制作 UMG序列帧动画制作相对比较简单&#xff0c;不像视频帧需要创建媒体播放器那么复杂&#xff0c;以下简要说明&#xff1a; 1. 事件函数 2. 准备序列帧装入数组 3. 构造调用事件函数 4. 预览 序列帧UMG0105 5. 完成&#xff01;按需配置即可。

【Mars3d】new mars3d.layer.GeoJsonLayer({不规则polygon加载label不在正中间的解决方案

问题&#xff1a; 1.new mars3d.layer.GeoJsonLayer({type: "polygon",在styleOptions里配置label的时候&#xff0c;发现这个 不规则polygon加载的时候&#xff0c;会出现label不在中心位置。 graphicLayer new mars3d.layer.GeoJsonLayer({ name: "全国省界…

macosx编译qgroundcontrol源码(Qt6.7)

1.克隆源码: clone --recursive http://github.com/mavlink/qgroundcontrol.git 克隆成功 3.编译 编译环境要求: 编译方法: 使用QtCreator编译 使用命令行编译 打开QGroundControl.pro并编译IOS版本 旧版本使用Qt 5.15.2 run qmake 新版本使用Qt 6.6或者更高 IOS工程输出要…

Oracle数据恢复记录一 表数据的恢复

当我们误删/修改数据之后&#xff0c;要进行数据恢复&#xff0c;需要有数据库管理员权限才能实现&#xff0c;所以奉劝各位修改数据要好好确认&#xff0c;搞出异常来就很麻烦了。下面是一个数据恢复简单的例子&#xff1a; DML Sql 这里展示了修改的sql UPDATE XX_MES_PROC…

使用STM32微控制器驱动LCD1602显示器

驱动LCD1602显示器是嵌入式系统常见的任务之一&#xff0c;而STM32微控制器因其灵活性和丰富的外设而成为了广泛采用的解决方案。在这篇文章中&#xff0c;我们将探讨如何使用STM32微控制器来驱动LCD1602显示器。我们将从STM32的GPIO配置、延时函数以及LCD1602的初始化和写入数…

23111 IO进程线程 day5

1>将互斥机制代码重新实现一遍 #include<myhead.h>char buf[128];//定义互斥锁变量 pthread_mutex_t mutex;//创建分支线程函数 void *task(void *arg) {while(1){//获取锁资源pthread_mutex_lock(&mutex);printf("分支线程中buf%s\n",buf);strcpy(buf…

Redis 教程

Redis 简介 Redis 是完全开源的&#xff0c;遵守 BSD 协议&#xff0c;是一个高性能的 key-value 数据库。 Redis 与其他 key - value 缓存产品有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可以将内存中的数据保存在磁盘中&#xff0c;重启的时候可以再次…

Apache Paimon:Streaming Lakehouse is Coming

摘要&#xff1a;本文整理自阿里云智能开源表存储负责人&#xff0c;Founder of Paimon&#xff0c;Flink PMC 成员李劲松&#xff08;花名&#xff1a;之信&#xff09;、同程旅行大数据专家&#xff0c;Apache Hudi & Paimon Contributor 吴祥平、汽车之家大数据计算平台…

移动神器RAX3000M路由器不刷固件变身家庭云之六(高级应用):设置https

本系列文章&#xff1a; 移动神器RAX3000M路由器变身家庭云之一&#xff1a;开通SSH&#xff0c;安装新软件包 移动神器RAX3000M路由器变身家庭云之二&#xff1a;安装vsftpd 移动神器RAX3000M路由器变身家庭云之三&#xff1a;外网访问家庭云 移动神器RAX3000M路由器变身家庭云…

数据库高可用mha

MHA搭建的步骤 一.配置主从复制 1.初始化环境 #在四台服务器上初始化环境 systemctl stop firewalld systemctl disable firewalld setenforce 0 2.修改 Master、Slave1、Slave2 节点的主机名 #在Master上 hostnamectl set-hostname mysql1 su#在Slave1 hostnamectl set-h…

WPF美化ItemsControl1:不同颜色间隔

首先我们有的是一个绑定好数据的ItemsControl <ItemsControl ItemsSource"{Binding Starts}"> </ItemsControl> 运行后呢是朴素的将数据竖着排列 如果想要数据之间有间距&#xff0c;可以使用数据模板&#xff0c;将数据放到TextBlock中显示&#xff0…

MATLAB - MPC - 优化问题(Optimization Problem)

系列文章目录 前言 模型预测控制可在每个控制间隔内解决一个优化问题&#xff0c;具体来说就是二次规划(QP)。求解结果决定了被控对象在下一个控制间隔之前使用的操纵变量&#xff08;MV&#xff09;。 该 QP 问题具有以下特点&#xff1a; 目标或 "成本 "函数 - …