PyScript详解:如何在浏览器上直接运行Python代码

PyScript详解:如何在浏览器上直接运行Python代码
近年来,Python 已成为最广泛使用的编程语言之一。然而,直到现在,Python 在 Web 开发领域才真正发挥了重要作用。PyScript 的出现改变了这一现状。它是一个全新的框架,允许您仅使用 HTML 和 Python 代码即可在 Web 浏览器上直接运行 Python 代码。无论您的经验水平如何,使用 PyScript 开发交互式 Web 应用都非常便捷,无需了解 JavaScript。在本教程中,您将了解 PyScript 的概念、工作原理以及如何使用它创建您的第一个基于浏览器的 Python 应用。

什么是PyScript

PyScript 是一个开源框架,它弥合了 Python 和 Web 之间的差距。它允许您直接在 Web 浏览器中运行 Python 代码。它允许您编写完全在客户端运行的交互式 Python 应用程序,而无需后端服务器。使用 PyScript 就像使用 Python 而不是 JavaScript 编写 Web 应用一样。您可以使用 Python 构建简单的交互式 Web 工具、仪表盘等。

PyScript 的主要功能

  1. 浏览器中的 Python:您可以在 HTML 文件中的 <py-script> 标签内编写 Python 代码
  2. 无需环境设置:无需安装任何其他库或工具。它在浏览器中运行。
  3. 与 HTML 交互:轻松将 Python 与 HTML、CSS 和 JavaScript 集成。
  4. 基于 WebAssembly:使用 Pyodide(将 Python 编译为 WebAssembly)在浏览器中运行 Python。

如何在Web应用中使用PyScript?

步骤 1:访问官方网站

访问官方网站。在这里,您可以浏览演示、文档并亲自尝试。

PyScript官网

Source: PyScript

PyScript项目

步骤 2:设置基本HTML文件

要运行 PyScript,您需要一个包含所需框架的简单 HTML 文件。

示例代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My First PyScript App</title>
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
  </head>
  <body>
    <h1>Hello from PyScript!</h1>
    <py-script>
      name = "PyScript"
      print(f"Hello, {name}! You are running Python in the browser.")
    </py-script>
  </body>
</html>

步骤 3:在浏览器中打开HTML文件

默认情况下,该文件包含 3 个文件:

main.py:

Your Python code.

Index.html:

The main web page that includes PyScript.

pyscript.toml:

A configuration file listing any extra Python packages youwant to use.

使用适当的代码更新代码文件并开始实验:

在浏览器中打开HTML文件

您可以在 PyScript 示例中尝试 PyScript Playground 以直接在浏览器中测试代码片段。

测试代码片段

PyScript动手实践

现在您已经熟悉了 PyScript 接口的工作原理,让我们来实际操作一下。

我们将构建一个双人井字棋游戏。

步骤 1:更新main.py

在 main.py 文件中添加 TicTacToe 类,该类包含游戏逻辑、用户交互和 UI 更新。它将使用 PyWeb 将 Python 与 HTML 连接起来,使游戏在浏览器中完全可交互。

代码:

from pyweb import pydom
class TicTacToe:
    def __init__(self):
        self.board = pydom["table#board"]
        self.status = pydom["h2#status"]
        self.console = pydom["script#console"][0]
        self.init_cells()
        self.init_winning_combos()
        self.new_game(...)
    def set_status(self, text):
        self.status.html = text
    def init_cells(self):
        self.cells = []
        for i in (0, 1, 2):
            row = []
            for j in (0, 1, 2):
                cell = pydom[f"div#cell{i}{j}"][0]
                assert cell
                row.append(cell)
            self.cells.append(row)
    def init_winning_combos(self):
        self.winning_combos = []
        # winning columns
        for i in (0, 1, 2):
            combo = []
            for j in (0, 1, 2):
                combo.append((i, j))
            self.winning_combos.append(combo)
        # winning rows
        for j in (0, 1, 2):
            combo = []
            for i in (0, 1, 2):
                combo.append((i, j))
            self.winning_combos.append(combo)
        # winning diagonals
        self.winning_combos.append([(0, 0), (1, 1), (2, 2)])
        self.winning_combos.append([(0, 2), (1, 1), (2, 0)])
    def new_game(self, event):
        self.clear_terminal()
        print('=================')
        print('NEW GAME STARTING')
        print()
        for i in (0, 1, 2):
            for j in (0, 1, 2):
                self.set_cell(i, j, "")
        self.current_player = "x"
experimenting        self.set_status(f'{self.current_player} playing...')
    def next_turn(self):
        winner = self.check_winner()
        if winner == "tie":
            self.set_status("It's a tie!")
            self.current_player = "" # i.e., game ended
            return
        elif winner is not None:
            self.set_status(f'{winner} wins')
            self.current_player = "" # i.e., game ended
            return
        if self.current_player == "x":
            self.current_player = "o"
        else:
            self.current_player = "x"
        self.set_status(f'{self.current_player} playing...')
    def check_winner(self):
        """
        Check whether the game as any winner.
        Return "x", "o", "tie" or None. None means that the game is still playing.
        """
        # check whether we have a winner
        for combo in self.winning_combos:
            winner = self.get_winner(combo)
            if winner:
                # highlight the winning cells
                for i, j in combo:
                    self.cells[i][j].add_class("win")
                return winner
        # check whether it's a tie
        for i in (0, 1, 2):
            for j in (0, 1, 2):
                if self.get_cell(i, j) == "":
                    # there is at least an empty cell, it's not a tie
                    return None # game still playing
        return "tie"
    def get_winner(self, combo):
        """
        If all the cells at the given points have the same value, return it.
        Else return "".
        Each point is a tuple of (i, j) coordinates.
        Example:
            self.get_winner([(0, 0), (1, 1), (2, 2)])
        """
        assert len(combo) == 3
        values = [self.get_cell(i, j) for i, j in combo]
        if values[0] == values[1] == values[2] and values[0] != "":
            return values[0]
        return ""
    def set_cell(self, i, j, value):
        assert value in ("", "x", "o")
        cell = self.cells[i][j]
        cell.html = value
        if "x" in cell.classes:
            cell.remove_class("x")
        if "o" in cell.classes:
            cell.remove_class("o")
        if "win" in cell.classes:
            cell.remove_class("win")
        if value != "":
            cell.add_class(value)
    def get_cell(self, i, j):
        cell = self.cells[i][j]
        value = cell.html
        assert value in ("", "x", "o")
        return value
    def click(self, event):
        i = int(event.target.getAttribute('data-x'))
        j = int(event.target.getAttribute('data-y'))
        print(f'Cell {i}, {j} clicked: ', end='')
        if self.current_player == "":
            print('game ended, nothing to do')
            return
        #
        value = self.get_cell(i, j)
        if value == "":
            print('cell empty, setting it')
            self.set_cell(i, j, self.current_player)
            self.next_turn()
        else:
            print(f'cell already full, cannot set it')
    def clear_terminal(self):
        self.console._js.terminal.clear()
    def toggle_terminal(self, event):
        hidden = self.console.parent._js.getAttribute("hidden")
        if hidden:
            self.console.parent._js.removeAttribute("hidden")
        else:
            self.console.parent._js.setAttribute("hidden", "hidden")
GAME = TicTacToe()

步骤 2:创建CSS文件

在新建的 assets 文件夹中创建一个 style.css 文件,用于定义井字游戏的布局和样式。这将处理棋盘、单元格以及所有状态消息的样式。

代码:

h1, h2 {
    font-family: 'Indie Flower', 'Comic Sans', cursive;
    text-align: center;
}
#board {
    font-family: 'Indie Flower', 'Comic Sans', cursive;
    position: relative;
    font-size: 120px;
    margin: 1% auto;
    border-collapse: collapse;
}
#board td {
    border: 4px solid rgb(60, 60, 60);
    width: 90px;
    height: 90px;
    vertical-align: middle;
    text-align: center;
    cursor: pointer;
}
#board td div {
    width: 90px;
    height: 90px;
    line-height: 90px;
    display: block;
    overflow: hidden;
    cursor: pointer;
}
.x {
    color: darksalmon;
    position: relative;
    font-size: 1.2em;
    cursor: default;
}
.o {
    color: aquamarine;
    position: relative;
    font-size: 1.0em;
    cursor: default;
}
.win {
    background-color: beige;
}

步骤 3:更新index.html

修改 index.html 文件,使其引用 PyScript 设置,加载 main.py 文件,定义游戏棋盘结构,并指向 style.css 文件(位于 assets 文件夹中)进行样式设置。

代码:

<!doctype html>
<html>
    <head>
        <!-- Recommended meta tags -->
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <!-- PyScript CSS -->
        <link rel="stylesheet" href="https://pyscript.net/releases/2024.1.1/core.css">
        <!-- CSS for examples -->
        <link rel="stylesheet" href="./assets/css/examples.css" />
        <!-- This script tag bootstraps PyScript -->
        <script type="module" src="https://pyscript.net/releases/2024.1.1/core.js"></script>
        <!-- Custom CSS -->
        <link href="https://fonts.googleapis.com/css?family=Indie+Flower" rel="stylesheet">
        <link rel="stylesheet" href="./assets/css/tictactoe.css" />
        <!-- for splashscreen -->
        <style>
            #loading { outline: none; border: none; background: transparent }
        </style>
        <script type="module">
            const loading = document.getElementById('loading');
            addEventListener('py:ready', () => loading.close());
            loading.showModal();
        </script>
        <title>Tic Tac Toe</title>
        <link rel="icon" type="image/png" href="./assets/favicon.png" />
    </head>
    <body>
        <dialog id="loading">
            <h1>Loading...</h1>
        </dialog>
        <nav class="navbar" style="background-color: #000000">
            <div class="app-header">
                <a href="/">
                    <img src="./assets/logo.png" class="logo" />
                </a>
                <a class="title" href="" style="color: #f0ab3c">Tic Tac Toe</a>
            </div>
        </nav>
        <section class="pyscript">
            <h1>Tic-Tac-Toe</h1>
            <script type="py" src="./main.py" config="./pyscript.toml"></script>
            <table id="board">
                <tr>
                    <td><div id="cell00" data-x="0" data-y="0" class="cell" py-click="GAME.click"></div></td>
                    <td><div id="cell01" data-x="0" data-y="1" class="cell" py-click="GAME.click"></div></td>
                    <td><div id="cell02" data-x="0" data-y="2" class="cell" py-click="GAME.click"></div></td>
                <tr>
                    <td><div id="cell10" data-x="1" data-y="0" class="cell" py-click="GAME.click"></div></td>
                    <td><div id="cell11" data-x="1" data-y="1" class="cell" py-click="GAME.click"></div></td>
                    <td><div id="cell12" data-x="1" data-y="2" class="cell" py-click="GAME.click"></div></td>
                </tr>
                <tr>
                    <td><div id="cell20" data-x="2" data-y="0" class="cell" py-click="GAME.click"></div></td>
                    <td><div id="cell21" data-x="2" data-y="1" class="cell" py-click="GAME.click"></div></td>
                    <td><div id="cell22" data-x="2" data-y="2" class="cell" py-click="GAME.click"></div></td>
                </tr>
            </table>
            <h2 id="status"></h2>
            <button id="btn-new-game" py-click="GAME.new_game">New game</button>
            <button id="btn-toggle-terminal" py-click="GAME.toggle_terminal">Hide/show terminal</button>
            <div id="terminal" hidden="hidden">
                <script id="console" type="py" terminal></script>
            </div>
        </section>
    </body>
</html>

步骤 4:更新pyscript.toml

使用应用所需的必要配置(包括依赖项、文件路径等)更新 pyscript.toml 文件。这可确保 PyScript 知道如何正确加载和运行 Python 代码。以下是我们的井字游戏应用的 pyscript.toml 文件内容:

配置:

name = "Tic Tac Toe"
description = "A Tic-Tac-Toe game written in PyScript that allows people to take turns."

输出:

这是您在 PScript 上的第一个项目。

小结

Python 在数据科学、人工智能、自动化和教育领域的应用前所未有。然而,迄今为止,Python 在 Web 上尚无原生平台。PyScript 应运而生,它将 Python 的简洁性与 Web 的易用性完美融合。它仍在不断完善,但已经为开发者、教育工作者和学习者创造了大量机会。

评论留言