Pyodide: 瀏覽器運行 Python
傳統上,Python 作為一種後端語言,主要在伺服器上運行。若想在網頁前端執行 Python,通常需要一個後端伺服器來處理請求並返回結果。然而,隨著技術發展,現在可以直接在瀏覽器中運行 Python,而 Pyodide 正是實現這一目標的關鍵技術。
Pyodide 讓你能夠在前端環境中,無需任何伺服器,直接執行 Python 代碼,甚至支持第三方套件。本文探討 Pyodide 的基礎知識,並透過一系列範例,學習如何使用 Pyodide。

上圖為在瀏覽器中運行 Python 的輸出
⚙️ Pyodide 如何運作?
要執行 Python 程式必要有一個 Python 直譯器,CPython 是主流的 Python 直譯器。
WebAssembly 是為瀏覽器設計的二進位指令格式,它是低階的組合語言,可以讓瀏覽器以接近原生的速度執行代碼。你可以把 C、C++ 等高效能語言編譯成 WebAssembly,然後在瀏覽器中運行。
Pyodide 是一個將 CPython 直譯器移植到 WebAssembly 的專案。CPython 直譯器已預先編譯為 WebAssembly,因此你是在瀏覽器執行 CPython 直譯器。
當你執行一段 Python 代碼時,實際流程是:
- JavaScript 將 Python 代碼字串傳遞給 Pyodide。
- 在 WebAssembly 環境中運行的 CPython 直譯器接收並解釋這段代碼。
- 執行結果可以返回給 JavaScript,或直接與瀏覽器環境互動。
🚀 如何在瀏覽器中加載 Pyodide
在網頁中使用 Pyodide 非常直接。最簡單的方法是透過 CDN 引入 Pyodide 的主腳本。
首先,創建一個基本的 HTML 檔案:
<!DOCTYPE html>
<html>
<head>
<!-- 加載 Pyodide -->
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.js"></script>
</head>
<body>
<h1>Pyodide 測試</h1>
<script type="text/javascript">
// 異步函數來加載和初始化 Pyodide
async function main() {
console.log("正在載入 Pyodide...");
let pyodide = await loadPyodide();
console.log("成功載入 Pyodide!");
// 這裡執行 Python 代碼 (未執行)
}
main();
</script>
<p>請按 F12 檢查瀏覽器 console 輸出</p>
</body>
</html>
loadPyodide()
是一個異步函數,它會返回一個 Promise。因此,我們需要使用 async/await
語法來等待 Pyodide 完全初始化。初始化完成後,pyodide
object 就成為了 JavaScript 與 Python 環境溝通的橋樑。
💻 執行你的第一個 Python 代碼
加載 Pyodide 後,你可以使用 pyodide.runPython()
方法來執行任意的 Python 代碼字串。
範例:基本的 print
預設情況下,Python 的 print
函數會將輸出發送到瀏覽器的開發者 Console。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.js"></script>
</head>
<body>
<p>請按 F12 檢查瀏覽器 console 輸出</p>
<script type="text/javascript">
async function main() {
let pyodide = await loadPyodide();
// 執行 Python 代碼
pyodide.runPython(`
import sys
# print 到 console
print("Hello from Python!")
print(f"Python version: {sys.version}")
`);
}
main();
</script>
</body>
</html>
打開此 HTML 檔案並查看 Console 的 Python 輸出。
📝 將輸出重定向到 DOM 元素
在 Console 中查看輸出很有用,但更多時候我們希望將結果直接顯示在網頁上。Pyodide 允許你在加載時配置標準輸出 (stdout
) 和標準錯誤 (stderr
) 的處理方式。
範例:將 print
輸出到 <pre>
標籤
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.js"></script>
</head>
<body>
<h2>Python 輸出:</h2>
<pre id="output"></pre>
<pre id="error"></pre>
<script type="text/javascript">
async function main() {
const output = document.getElementById('output');
const error = document.getElementById('error');
// 在加載時傳入配置 object
let pyodide = await loadPyodide({
// 將 stdout 重定向到一個 JS 函數
stdout: (text) => {
output.textContent += text + '\n';
},
// 將 stderr 也重定向
stderr: (text) => {
error.textContent += text + '\n';
}
});
// 執行 Python 代碼,輸出將顯示在頁面上
pyodide.runPython(`
print("直接把文字顯示在網頁的 pre 標籤中。")
x = 10
y = 20
# print 到 DOM 上,而不是 console
print(f"{x} + {y} = {x + y}")
# 模擬一個錯誤
import sys
sys.stderr.write("這是一個從 stderr 發出的錯誤訊息。\\n")
`);
}
main();
</script>
</body>
</html>
在這個範例中,我們將 stdout
和 stderr
指向一個 JavaScript 函數,該函數會將接收到的文字附加到指定的 DOM 元素上,而不是在 console 上。
🌐 從 Python 操作網頁 DOM
Pyodide 提供了一個名為 js
的內置模組,它充當了 Python 和瀏覽器 JavaScript 環境之間的橋樑。透過 import js
,你的 Python 代碼可以存取 window
、document
等全局 JavaScript object,從而實現對 DOM 的直接操作。
範例:使用 Python 創建和修改 DOM 元素
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.js"></script>
</head>
<body>
<h2>使用 Pyodide 動態操作 DOM</h2>
<p>輸入文字並點擊按鈕,Python 會將其新增到頁面中。</p>
<input type="text" id="text-input" placeholder="在此輸入文字...">
<button id="add-btn">用 Python 新增</button>
<div id="container"></div>
<script type="text/javascript">
async function main() {
// 載入 Pyodide 並設定控制台輸出
let pyodide = await loadPyodide();
pyodide.setStdout((text) => { console.log("Python:", text); });
const pythonCode = `
import js
from pyodide.ffi import create_proxy
# --- 核心邏輯在這個 Python 函式中 ---
def add_element_from_input(event):
# 從輸入框獲取使用者輸入的文字
input_element = js.document.getElementById('text-input')
user_text = input_element.value
# 如果輸入為空,不新增內容
if not user_text:
return
# 建立新的 <p>
new_paragraph = js.document.createElement('p')
new_paragraph.textContent = "Python 新增內容:" + user_text
# 把新的 <p> 加到 container 中
container = js.document.getElementById('container')
container.appendChild(new_paragraph)
input_element.value = "" # 清除輸入
# --- 事件 ---
button = js.document.getElementById('add-btn')
# 為 Python 函式建立一個 proxy,並傳入一個 function
add_element_proxy = create_proxy(add_element_from_input)
# 當按鈕被點擊時,通過 proxy 執行 Python function
button.addEventListener('click', add_element_proxy)
`;
// 5. 執行 Python 程式碼以設定事件監聽器
await pyodide.runPythonAsync(pythonCode);
}
main();
</script>
</body>
</html>
在這個範例中,Python 代碼使用 js.document
來操作 DOM。特別注意 create_proxy
的使用,當你需要將一個 Python 函數作為回調(如事件監聽器)傳遞給 JavaScript 時,必須使用它來創建一個代理。
📦 套件管理:內置與 micropip
Pyodide 的強大之處在於其豐富的套件支援。它內置了大量經過優化的科學計算套件,這些套件也同樣被編譯成了 WebAssembly,以確保高效能。
對於那些未被內置的純 Python 套件,Pyodide 提供了 micropip
工具。micropip
可以在運行時從 Python 套件索引 (PyPI) 下載並安裝 wheel 格式的套件。
以下是一些常見套件的分類:
類型 | 套件範例 | 說明 |
---|---|---|
內置套件 | numpy , pandas , matplotlib , scikit-learn , scipy 等 | 已預先編譯為 WebAssembly,加載速度快,效能高。 |
其他套件,透過 micropip 安裝 | requests , beautifulsoup4 , pytest , Pillow , lxml 等 | 從 PyPI 下載純 Python wheel 檔案並安裝,無法安裝需要 C 編譯的套件。 |
📥 使用 micropip 安裝套件及下載數據
現在來看看如何安裝外部套件。首先需要用 pyodide.loadPackage()
加載 micropip
本身,然後就可以在 Python 代碼中用它來安裝其他套件了。
範例:安裝 requests
並從 API 獲取數據
requests
在 Pyodide 中會被自動轉換為使用瀏覽器內置的 fetch
API,因此所有網絡請求都會受到同源策略(CORS)的限制。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.js"></script>
</head>
<body>
<h2>使用 micropip 安裝 Requests 套件</h2>
<pre id="output" style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<script>
const output = document.getElementById('output');
function logToPage(message) {
output.textContent += message + '\n';
}
async function main() {
try {
logToPage("正在初始化 Pyodide...");
let pyodide = await loadPyodide();
logToPage("Pyodide 初始化完成。");
pyodide.setStdout({ batched: logToPage });
pyodide.setStderr({ batched: logToPage });
logToPage("正在準備安裝套件 (micropip)...");
await pyodide.loadPackage("micropip"); // 加載 micropip 套件
logToPage("Micropip 準備完成。");
const pythonCode = `
import micropip
import json
# 1. 用 micropip 安裝套件
print("--> 正在安裝 'requests'...")
await micropip.install('requests') # 安裝 requests
print("--> 'requests' 安裝成功!")
# 2. 導入套件
import requests
print("\\n--> 正在從 API 獲取數據...")
url = "https://jsonplaceholder.typicode.com/todos/1"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
pretty_data = json.dumps(data, indent=2, ensure_ascii=False)
print("--> 成功獲取數據:")
print(pretty_data)
except Exception as e:
print(f"--> 獲取數據失敗: {e}")
`;
logToPage("\n--- 開始執行 Python 程式 ---");
await pyodide.runPythonAsync(pythonCode);
logToPage("--- Python 程式執行完畢 ---");
} catch (error) {
logToPage(`\n*** 發生嚴重錯誤: ${error} ***`);
}
}
main();
</script>
</body>
</html>
runPythonAsync
是 runPython
的異步版本,當 Python 代碼中包含 await
(例如 await micropip.install()
)時,必須使用它。
📊 將數據加載到 Pandas DataFrame
結合前面的例子,現在將獲取到的數據加載到 Pandas DataFrame 中,並將結果以 HTML 表格的形式呈現在網頁上。
範例:獲取數據並用 Pandas 處理
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.1/full/pyodide.js"></script>
</head>
<body>
<h2>使用 Pandas 處理 API 數據</h2>
<div id="console-output"></div>
<h3>Pandas DataFrame (HTML 格式):</h3>
<div id="pandas-output"></div>
<script>
async function main() {
const consoleOutput = document.getElementById('console-output');
let pyodide = await loadPyodide();
pyodide.setStdout((text) => { consoleOutput.textContent += text + '\n'; });
consoleOutput.textContent = "正在加載 pandas 和 micropip...\n";
await pyodide.loadPackage(['pandas', 'micropip']); // 加載套件
consoleOutput.textContent += "套件加載完成。\n";
const pythonCode = `
import micropip
await micropip.install('requests')
import requests
import pandas as pd
import js
print("正在從 API 獲取用戶數據...")
url = "https://jsonplaceholder.typicode.com/users"
response = requests.get(url)
data = response.json()
print("數據獲取成功,正在創建 DataFrame...")
df = pd.DataFrame(data)
# 僅選擇部分欄位
df_subset = df[['id', 'name', 'username', 'email', 'phone']]
print("DataFrame Head (顯示在 Console):")
print(df_subset.head())
# 將 DataFrame 轉換為 HTML 表格
html_table = df_subset.to_html(classes='table', index=False)
# 使用 js 模組將 HTML 插入到 DOM 中
js.document.getElementById('pandas-output').innerHTML = html_table
`;
await pyodide.runPythonAsync(pythonCode);
}
main();
</script>
</body>
</html>
在這個例子中,我們:
- 使用
pyodide.loadPackage()
一次性加載多個內置套件 (pandas
,micropip
)。 - 在 Python 中安裝
requests
並獲取數據。 - 使用
pd.DataFrame()
創建 DataFrame。 - 使用
df.to_html()
將 DataFrame 轉換成 HTML 字串。 - 透過
js.document.getElementById().innerHTML
將 HTML 表格渲染到網頁上。
🧩 綜合應用:一個簡單的 Python REPL
最後,讓我們將前面學到的所有知識整合起來,創建一個簡單的、互動式的 Python REPL (Read-Eval-Print Loop) 網頁應用。

功能:
- 一個文本框用於輸入 Python 代碼。
- 一個按鈕用於執行代碼。
- 一個區域用於顯示輸出結果和錯誤。
這個綜合範例展示了 Pyodide 的真正潛力。用戶可以在網頁上自由編寫和執行 Python 代碼,包括使用 pandas
和 numpy
等強大工具,而所有計算都在用戶的瀏覽器中完成,完全不需後端伺服器。