html中运行python代码
文章说明
文章初始是为了实现在HTML页面中运行python的绘图代码,其中主要为turtle的代码,然后先后采用了 pyodide、flask、Brython 这三个技术
最初采用 pyodide 的时候研究了蛮久的,但是一直不行,无法将绘图绑定到页面元素
后续转为采用 flask 进行服务提供,然后在后台绘图,绘图完成后将内容传到前台;效果上还是可以的,但是仍然存在着环境部署相关的复杂性
最后采用 Brython 来实现绘图操作,效果不错,且环境部署简单
下面简单介绍这三个内容
pyodide的绘图版本
- 采用pyodide实现在html中运行python代码
- 由于采用cdn下来pyodide的相关资源的话,会下的很慢,然后报错,可直接采用本地加载
- 由于无法绑定页面元素,所以无法实现turtle的绘图操作
核心代码
运行简单的python代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>使用 Pyodide 的简单示例</title>
<script src="./resource/pyodide.js"></script>
</head>
<body>
<h1>欢迎来到 Pyodide 示例页面</h1>
<button type="button" onclick="runPythonCode()">点击运行 Python 代码</button>
<script type="text/javascript">
let pyodide;
async function initPyodide() {
// 初始化 Pyodide
pyodide = await loadPyodide();
alert("Pyodide 已初始化");
}
async function runPythonCode() {
if (!pyodide) {
alert("正在初始化,请稍后再试...");
return;
}
// 运行一些 Python 代码
let result = pyodide.runPython(`
def say_hello(name):
return "你好, " + name + "!"
say_hello("世界")
`);
// 显示结果
alert(result);
}
// 页面加载完成后初始化 Pyodide
window.onload = async () => {
await initPyodide();
};
</script>
</body>
</html>
运行turtle代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Pyodide Turtle 示例</title>
<style>
.container {
display: flex;
height: 90vh;
}
#editor {
width: 50%;
padding: 10px;
box-sizing: border-box;
}
#canvas-container {
width: 50%;
position: relative;
}
canvas {
border: 1px solid black;
width: 100%;
height: 100%;
}
</style>
<!-- 加载 Pyodide -->
<script src="./resource/pyodide.js"></script>
</head>
<body>
<div class="container">
<textarea id="editor" placeholder="在这里输入turtle代码...">
import turtle
t = turtle.Turtle()
t.forward(100)
t.left(90)
t.forward(100)
</textarea>
<div id="canvas-container">
<canvas id="myCanvas"></canvas>
</div>
</div>
<button onclick="runTurtleCode()">运行 Turtle 代码</button>
<script type="text/javascript">
let pyodide;
async function initPyodide() {
// 初始化 Pyodide
pyodide = await loadPyodide({
indexURL: "./resource/"
});
alert("Pyodide 已初始化");
}
async function installTurtlePackage() {
await pyodide.loadPackage("./resource/turtle-0.0.1-py3-none-any.whl");
}
async function runTurtleCode() {
if (!pyodide) {
alert("正在初始化,请稍后再试...");
return;
}
const editor = document.getElementById('editor');
const code = editor.value;
try {
// 清空画布内容
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 设置canvas大小
canvas.width = 600;
canvas.height = 400;
// 如果未安装turtle包,则先安装
if (!pyodide.globals.has('turtle')) {
await installTurtlePackage();
}
// 使用 Pyodide 运行 Python turtle 代码
await pyodide.runPythonAsync(`
from js import document
import turtle
# 设置turtle使用的canvas
# screen = turtle.Screen()
# screen.cv._js_object = document.getElementById('myCanvas')
`);
// 执行用户提供的turtle代码
await pyodide.runPythonAsync(code);
} catch (error) {
console.log(error);
alert("执行出错:" + error.toString());
}
}
window.onload = async () => {
await initPyodide();
};
</script>
</body>
</html>
效果展示
flask实现绘图
采用flask提供绘图服务,在后台进行绘制,绘制完成后将图像传到前台展示
核心源码
app.py
from flask import Flask, render_template_string, send_file, request
import io
import base64
import turtle
from PIL import Image
from multiprocessing import Process, Queue
import os
app = Flask(__name__)
@app.route('/')
def index():
template = '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turtle Graphics</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e6f7ff; /* 浅蓝色背景 */
color: #333;
}
.container {
text-align: center;
background: #d9f0ff; /* 浅蓝色背景 */
padding: 30px;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
}
h1 {
margin-bottom: 20px;
color: #007bff; /* 深蓝色标题 */
}
textarea {
width: 100%;
height: 150px;
margin-bottom: 20px;
padding: 10px;
border: 1px solid #a0d9ff; /* 浅蓝色边框 */
border-radius: 8px;
font-size: 14px;
resize: none;
transition: border-color 0.3s ease;
box-sizing: border-box;
outline: none;
}
textarea:focus {
border-color: #007bff; /* 聚焦时的深蓝色边框 */
}
canvas {
border: 1px solid #a0d9ff; /* 浅蓝色边框 */
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
button {
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
background-color: #007bff; /* 深蓝色按钮 */
color: white;
border: none;
border-radius: 8px;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #0056b3; /* 悬停时更深的蓝色 */
}
</style>
</head>
<body>
<div class="container">
<h1>Turtle Graphics Demo</h1>
<textarea id="turtleCode" placeholder="Enter your Turtle code here"></textarea>
<canvas id="turtleCanvas" width="500" height="500"></canvas>
<button onclick="drawTurtleGraphics()">Draw Turtle Graphics</button>
</div>
<script>
function drawTurtleGraphics() {
const turtleCode = document.getElementById('turtleCode').value;
fetch('/image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ code: turtleCode })
})
.then(response => response.blob())
.then(blob => {
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = () => {
const canvas = document.getElementById('turtleCanvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
});
}
</script>
</body>
</html>
'''
return render_template_string(template)
def run_turtle_code(user_code, queue):
"""执行Turtle代码并返回结果"""
screen = turtle.Screen()
screen.setup(500, 500)
t = turtle.Turtle()
t.speed(0)
try:
exec(user_code, {'turtle': turtle, 't': t})
canvas = screen.getcanvas()
ps = canvas.postscript(colormode='color')
img = Image.open(io.BytesIO(ps.encode('utf-8')))
queue.put(img)
except Exception as e:
queue.put(str(e))
finally:
turtle.bye()
@app.route('/image', methods=['POST'])
def image():
user_code = request.json.get('code', '')
queue = Queue()
p = Process(target=run_turtle_code, args=(user_code, queue))
p.start()
result = queue.get()
p.join()
if isinstance(result, str): # 如果结果是一个字符串,则它代表了一个异常信息
return result, 500
png_img_io = io.BytesIO()
result.save(png_img_io, format='PNG')
png_img_io.seek(0)
return send_file(png_img_io, mimetype='image/png')
if __name__ == '__main__':
app.run(debug=True)
效果展示
Brython实现绘图
采用Brython 可在JavaScript直接运行python代码
核心源码
测试图像绘制页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Brython with Turtle</title>
<script src="./brython.min.js"></script>
<script src="./brython_stdlib.min.js"></script>
<script src="./turtle.min.js"></script>
</head>
<body onload="brython()">
<h1>Turtle Graphics in Brython</h1>
<script type="text/python">
from turtle import *
def draw_square():
for _ in range(4):
forward(100)
right(90)
def main():
speed('fastest')
draw_square()
done()
main()
</script>
</body>
</html>
效果展示
Brython实现绘图-弹窗展示版本
采用Brython 可在JavaScript直接运行python代码,调整为页面输入代码,同时弹窗展示绘图结果,然后将输出重定向到页面的展示元素
核心源码
测试图像绘制页面
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Brython with Turtle</title>
<script src="./brython.min.js"></script>
<script src="./brython_stdlib.min.js"></script>
<script src="./turtle.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
h1 {
color: #333;
}
.container {
max-width: 800px;
margin: auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
textarea {
width: 100%;
height: 300px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
box-sizing: border-box;
outline: none;
resize: none;
font-size: 1.2rem;
}
textarea:focus {
border: 1px solid #007bff;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
#turtle-canvas-wrapper {
display: none !important;
}
</style>
</head>
<body onload="brython()">
<div class="container">
<h1>Turtle Graphics in Brython</h1>
<textarea id="turtleCode" placeholder="Enter your Turtle code here..."></textarea>
<button onclick="runTurtleCode()" id="run">Run</button>
<pre id="outputConsole"
style="background-color: #eee; padding: 10px; border-radius: 4px; margin-top: 10px;"></pre>
</div>
<script type="text/javascript">
function runTurtleCode() {
const code = document.getElementById('turtleCode').value;
// 使用时间戳来创建一个唯一的窗口名称
const windowName = 'TurtleGraphics_' + new Date().getTime();
const popupWindow = window.open('', windowName, 'width=800,height=600');
popupWindow.document.write(`
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>Turtle Graphics Result</title>
<script src="./brython.min.js"><\/script>
<script src="./brython_stdlib.min.js"><\/script>
<script src="./turtle.min.js"><\/script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f4f4f4;
}
</style>
</head>
<body onload="brython()">
<script type="text/python">${code}<\/script>
</body>
</html>
`);
popupWindow.document.close();
document.getElementById("outputConsole").innerHTML = "";
}
</script>
<script type="text/python">
import sys
from browser import document
class MyOutput:
def __init__(self):
self.console = document["outputConsole"]
def write(self, text):
self.console.text += text
sys.stdout = MyOutput()
def run(event):
code = document["turtleCode"].value
exec(code)
document["run"].bind("click", run)
</script>
</body>
</html>
效果展示
源码下载
参考资料
其中Brython的输出重定向参考了这篇文章:如何将所有 Brython 输出重定向到 textarea 元素
同时Brython也参考了官方的示例demo:brython
其中flask的绘图版本中,还参考了Ghostscript的安装指导:python利用Turtle绘图并保存jpg