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

news2025/1/11 12:58:50

支持 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_str
        raise 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-input
            type="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-input
            type="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.event
                        if (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 Timer

import psutil


class AbstractExecutor:

    def __init__(self, param):
        # param 包括 code、input_queue
        self.param = param
        # 用于保护 is_timeout 的锁
        self.lock = threading.Lock()
        # 是否执行超时了
        self.is_timeout = None

    def timeout_callback(self, p: subprocess.Popen):
        """
        执行超时时的回调,会终止执行 python 代码的进程组
        :param p: 执行 python 代码的进程
        """
        with self.lock:
            if self.is_timeout is None:
                self.is_timeout = True

        if 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_queue
            p.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 = False

        finally:
            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 重定向到了 stdout
        return 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.pid
        parent_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 output


if os.name == "nt":
    executor_cls = WindowsExecutor


def 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 sys


input_queue = []


class InputRequestException(Exception):
    """
    抛出此异常表示需要用户输入
    """
    pass


def execute(param):
    # 重写 input 函数
    __builtins__.input = input_wrapper

    # input_queue
    global input_queue
    input_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_queue
    arg = 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.coloradmin.cn/o/1361916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[NAND Flash 5.2] SLC、MLC、TLC、QLC、PLC NAND_闪存颗粒类型

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解NAND Flash》 <<<< 返回总目录 <<<< 前言 闪存最小物理单位是 Cell, 一个Cell 是一个晶体管。 闪存是通过晶体管储存电子来表示信息的。在晶体管上加入了浮动栅贮存电子…

网络调试 TCP,开发板用静态地址-入门7

用两台电脑&#xff08;无线网络&#xff09;做实验 1.1, 在电脑A上设置为Server如下&#xff1a; 选择TCP Server后&#xff0c;直接跳出用本机IP做为“本地主机地址” 1.2在 电脑B上设置为Client, 远程主机地址设置为Server的 IP 1.3, 在A, B两台电脑上能够互相发送数据 用…

Pyinstaller 打包的文件过大,根本原因在于包含了无用的依赖文件

环境要求&#xff1a;Windows&#xff0c;Pyinstaller 6.3.0 (不是这个版本的话&#xff0c;请逐步校对以下我在运行过程中的截图) 本文所使用代码不开源&#xff0c;觉得本文的思路可行的话&#xff0c;请加 QQ - 1398173074 购买 (&#xffe5;30&#xff0c;注明来意)&…

算力-计算量,关于TOPS和FLOPS,及FLOPs

目录 一、易混淆的三个点 二、芯片算力单位详解 四、模型算力单位详解 五、算力单位量级变换 六、计算机存储单位换算 七、科学计算单位换算 一、易混淆的三个点 关于TOPS和FLOPS&#xff0c;及FLOPs&#xff0c;这里有3个易混淆的点。 1、最大的混淆点&#xff0c…

MybatisPlus—快速入门

目录 1.使用MybatisPlus的基本步骤 1.1引入MybatisPlus的起步依赖 1.2 定义Mapper 2.MybatisPlus常用注解 2.1 TableName 2.2 TableId 2.3 TableField 2.4 小结 3. 常用配置 4. 总结 1.使用MybatisPlus的基本步骤 1.1引入MybatisPlus的起步依赖 MyBatisPlus官方提…

(04)刻蚀——选择刻蚀材料创建所需图形

01、光“堆叠”可不行 前期我们了解了如何制作“饼干模具”。本期,我们就来讲讲如何采用这个“饼干模具”印出我们想要的“饼干”。这一步骤的重点,在于如何移除不需要的材料,即“刻蚀(Etching)工艺”。 ▲ 图1: 移除饼干中间部分,再倒入巧克力糖浆 让我们再来回想一下…

im6ull学习总结(三-3)freetype

1、Freetype简介 FreeType是一个开源的字体渲染引擎&#xff0c;主要用于将字体文件转换为位图或矢量图形&#xff0c;并在屏幕上渲染出高质量的字体。它提供了一组API&#xff0c;使开发者能够在自己的应用程序中使用和呈现字体。 FreeType最初是作为一个独立项目开发的&…

基于X86的助力智慧船载监控系统

船载综合监控系统结合雷达、AIS、CCTV、GPS等探测技术&#xff0c;以及高度融合的实时态势与认知技术&#xff0c;实现对本船以及范围内船舶的有效监控&#xff0c;延伸岸基监控中心监管范围&#xff0c;保障行船安全&#xff0c;为船舶安全管理部门实现岸基可控的数据通信和动…

程序员必知!责任链模式的实战应用与案例分析

责任链模式让多个对象依次处理请求&#xff0c;降低发送者和接收者的耦合度&#xff0c;以在线购物为例&#xff0c;用户提交订单需经多步验证&#xff0c;通过责任链模式&#xff0c;验证器按顺序处理请求&#xff0c;先用户身份&#xff0c;再支付方式&#xff0c;最后配送地…

odoo16 连接postgresql错误

odoo16 连接postgresql错误 odoo16 用odoo15的环境出错&#xff0c;看到是psycopg2.OperationalError分析是postgresql版本问题&#xff0c;安装了13版本&#xff0c;还是出错&#xff0c;多版本共存问题如下&#xff1a; Traceback (most recent call last):File "D:\o…

@JsonFormat与@DateTimeFormat

JsonFormat注解很好的解决了后端传给前端的格式&#xff0c;我们通过使用 JsonFormat可以很好的解决&#xff1a;后台到前台时间格式保持一致的问题 其次&#xff0c;另一个问题是&#xff0c;我们在使用WEB服务的时&#xff0c;可 能会需要用到&#xff0c;传入时间给后台&am…

书生·浦语大模型实战营第一次课堂笔记

书生浦语大模型全链路开源体系。大模型是发展通用人工智能的重要途径,是人工通用人工智能的一个重要途径。书生浦语大模型覆盖轻量级、重量级、重量级的三种不同大小模型,可用于智能客服、个人助手等领域。还介绍了书生浦语大模型的性能在多个数据集上全面超过了相似量级或相近…

算法训练第六十天|84.柱状图中最大的矩形

84.柱状图中最大的矩形&#xff1a; 题目链接 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 : 输入&#xff1a;heights [2,1,5,6,2,3] 输出…

基于JavaWeb+SSM+Vue家政项目微信小程序系统的设计和实现

基于JavaWebSSMVue家政项目微信小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 目录 1系统概述 1 1.1 研究背景 1 1.2研究目的 1 1.3系统设计思想 1 2相关技术 2…

Mathtype7.4安装与嵌入WPS

文章目录 Mathtype安装教程&#xff08;7.4&#xff09;Mathtype简介Mathtype下载安装软件下载软件安装运行MathType.exe运行注册表 Mathtype嵌入wps Mathtype安装教程&#xff08;7.4&#xff09; Mathtype简介 MathType是一款强大的数学公式编辑器&#xff0c;适用于教育教…

【大数据实验系列】一文轻松搞定云服务器Centos8.x下安装MySQL5.x版本,以阿里云服务器为例!(超详细安装流程)

1. 文章主要内容 本篇博客主要涉及云服务器(以阿里云服务器为例子)Centos8.x下安装MySQL软件。&#xff08;通读本篇博客需要10分钟左右的时间&#xff09;。 本篇博客内容参考于&#xff1a;centOS8安装MySql5.7 2. 详细安装教程 2.1 MySQL rpm源包下载 我们首先点击…

玩转爱斯维尔 LaTeX 模板:定制技巧一网打尽!

简介 关于 LaTeX 小编写过一些推文&#xff1a; 适合撰写课程论文的 LaTeX 模板; LaTeX 常用数学符号汇总; 免费升级 overleaf 高级账户&#xff01;; 如何下载使用期刊的 LaTeX 模板 本文基于常用的 Elsevier 期刊模板&#xff0c;小编分享个人常用的使用技巧&#xff0…

SpringCloud-高级篇(十一)

&#xff08;1&#xff09;搭建Redis-主从架构 前面我们实现了Redis的持久化&#xff0c;解决了数据安全问题&#xff0c;但是还有需要解决的问题&#xff0c;下面学习Redis的主从集群&#xff0c;解决Redis的并发能力的问题 Redis的集群往往是主从集群&#xff0c;Redsi为什么…

时间序列预测 — LSTM实现多变量多步负荷预测(Tensorflow):多输入多输出

目录 1 数据处理 1.1 导入库文件 1.2 导入数据集 ​1.3 缺失值分析 2 构造训练数据 3 LSTM模型训练 4 LSTM模型预测 4.1 分量预测 4.2 可视化 1 数据处理 1.1 导入库文件 import time import datetime import pandas as pd import numpy as np import matplotlib.p…