支持 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_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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

查询json数组

步骤一&#xff1a;创建表格 首先&#xff0c;我们需要创建一个表格来存储包含JSON对象数组的数据。可以使用以下代码创建一个名为 my_table 的表格&#xff1a; CREATE TABLE my_table (id INT PRIMARY KEY AUTO_INCREMENT,json_data JSON ); 上述代码创建了一个包含两个列的…

算法实验T15——POJ1636 Prison rearrangement

题目描述 Prison rearrangement Time Limit: 3000MSMemory Limit: 10000KTotal Submissions: 6415Accepted: 2718 Description&#xff1a; In order to lower the risk of riots and escape attempts, the boards of two nearby prisons of equal prisoner capacity, have dec…

计算机毕业设计 SpringBoot的中小型制造企业质量管理系统 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

Kafka安全认证机制详解之SASL_PLAIN

一、概述 官方文档&#xff1a; https://kafka.apache.org/documentation/#security 在官方文档中&#xff0c;kafka有五种加密认证方式&#xff0c;分别如下&#xff1a; SSL&#xff1a;用于测试环境SASL/GSSAPI (Kerberos) &#xff1a;使用kerberos认证&#xff0c;密码是…

Ubuntu 本地部署 ChatGPT-Next-Web

Ubuntu 本地部署 ChatGPT-Next-Web 文章目录 Ubuntu 本地部署 ChatGPT-Next-Web ChatGPT-Next-Web 项目地址&#xff1a;https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 本文主要演示如何在 Ubuntu 本地&#xff08;默认是端口 3000&#xff09;部署 ChatGPT-Next-Web&am…

差分约束算法

差分约束 差分约束系统包含 m m m个涉及 n n n个变量的差额限制条件&#xff0c;这些差额限制条件每个都是形式为 x i − x j ≤ b ∈ [ 1 , m ] x_i-x_j\leq b_{\in[1,m]} xi​−xj​≤b∈[1,m]​的简单线性不等式。 通常我们要求解出一组可行解。 最短路差分约束 如果我们…

ejs默认配置 造成原型链污染

文章目录 ejs默认配置 造成原型链污染漏洞背景漏洞分析漏洞利用 例题 [SEETF 2023]Express JavaScript Security ejs默认配置 造成原型链污染 参考文章 漏洞背景 EJS维护者对原型链污染的问题有着很好的理解&#xff0c;并使用非常安全的函数清理他们创建的每个对象 利用Re…

苹果macOS 14.3开发者预览版Beta 2发布 修复API会意外失败的问题

1 月 4 日消息&#xff0c;苹果向 Mac 电脑用户推送了 macOS 14.3 开发者预览版 Beta 2 更新&#xff08;内部版本号&#xff1a;23D5043d&#xff09;&#xff0c;本次更新距离上次发布隔了 22 天。 macOS Sonoma 14.3 Beta 2 主要以修复 BUG、提高安全性为主。根据苹果官方更…

VS+QT五子棋游戏开发

1、首先安装好VS软件和QT库&#xff0c;将其配置好&#xff0c;具体不在此展开说明。 2、文件结构如下图&#xff1a; 3、绘制棋盘代码&#xff0c;如下&#xff1a; void Qwzq::paintEvent(QPaintEvent* event) {QPainter painter(this);painter.setRenderHint(QPainter::An…

线性代数第一课+第二课总结

第一课 第一课是简单的行列式计算&#xff0c;主要就是要把左下角的数字全部转换为0&#xff0c;通过减去其他行的式子即可实现&#xff0c;最后把对角线的所有数字相乘&#xff0c;得到的结果是最后行列式的答案 第二课 例题1 硬算理论上其实也是可行的&#xff0c;但是使…

遇见狂神说 Spring学习笔记(完整笔记+代码)

简介 Spring是一个开源的免费的框架&#xff08;容器&#xff09;Spring是一个轻量级的、非入侵式的框架控制反转(IOC&#xff09;&#xff0c;面向切面编程 (AOP)支持事务的处理&#xff0c;支持对框架进行整合 Spring就是一个轻量级的控制反转&#xff08;IOC&#xff09;和…

亚马逊云科技基于 listmonk 的电子邮件营销解决方案

本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道。 背景 电子邮件营销&#xff08;EDM&#xff09;在广告、电商、供应链物流等行业应用…

Linux操作系统基础(10):Linux的特殊权限

1. 特殊权限是什么 在Linux中&#xff0c;特殊权限是指针对文件或目录的特殊权限设置&#xff0c;包括SetUID、SetGID和Sticky Bit。 SetUID&#xff08;Set User ID&#xff09;&#xff1a; 当一个可执行文件被设置了SetUID权限后&#xff0c;当任何用户执行该文件时&#x…

UI5与后端的文件交互(二)

文章目录 前言一、开发Action1. 创建Structure2. BEDF添加Action3. class中实现Action 二、修改UI5 项目1. 添加一个按钮2. 定义事件函数 三、测试及解析1. 测试2. js中提取到的excel流数据3. 后端解析 前言 这系列文章详细记录在Fiori应用中如何在前端和后端之间使用文件进行…

数字图像处理(图像灰度变换、图像直方图及均衡、图像中值滤波、图像空域锐化增强、图像频域滤波)

数字图像处理&#xff08;图像灰度变换、图像直方图及均衡、图像中值滤波、图像空域锐化增强、图像频域滤波&#xff09; 目录 1 图像灰度变换 1.1 灰度线性变换 1.2 图像二值化 1.3 负象变换 1.4 灰度非线性变换 1.5 程序设计流程图 2 图像直方图及均衡 2.1 直方图 2…

了解单元测试

一&#xff0c;测试分类 1.1 E2E测试&#xff08;end to end端到端测试&#xff09; 属于黑盒测试。 主要通过测试框架&#xff0c;站在用户测试人员的角度&#xff0c;模拟用户的操作进行页面功能的验证&#xff0c;不管内部实现机制&#xff0c;完全模拟浏览器的行为。&am…

Pytest——Fixture夹具的使用

一、什么是Fixture 在测试开展的过程中&#xff0c;会需要考虑到测试前的准备工作&#xff0c;以及测试后的释放操作行为。这些在Pytest中&#xff0c;会通过Fixture的方式来实现。如果说在运行pytest的测试用例的时候&#xff0c;需要调用一些数据来实现测试行为&#xff0c;…

thingsboard规则节点功能记录(自用)

本文是对【ThingsBoard源码级分析规则节点使用第一季】 https://www.bilibili.com/video/BV1CT411e7vt/?p4&share_sourcecopy_web&vd_source9a5ca7ed3cff97385fdab4b6188e485c 学习的一些记录&#xff0c;加深自己的理解&#xff0c;在此声明。 asset profile switch…

五、HTML 标题

在 HTML 文档中&#xff0c;标题很重要。 一、HTML 标题 标题&#xff08;Heading&#xff09;是通过 <h1> - <h6> 标签进行定义的。<h1> 定义最大的标题。 <h6> 定义最小的标题。 <h1>这是一个标题。</h1> <h2>这是一个标题。&l…

微型导轨在设备中起什么作用

微型导轨精度高&#xff0c;摩擦系数小&#xff0c;自重轻&#xff0c;结构紧凑&#xff0c;可以用于电子制造设备、半导体制造设备、医疗设备、光学设备和机器人等各种工业机械设备中&#xff0c;那么微型导轨在设备中起什么作用呢&#xff1f; 1、导向与定位&#xff1a;为机…