Python 入門指南 5.0

單元 41 - Brython 與 JavaScript

~~學習進度表~~

如果要在瀏覽器 (browser) 中執行 Python 程式,有數種方案,其中之一便是 Brython 直譯器 (interpreter)

Python 程式碼 -> Brython 直譯器

Brython 是用 JavaScript 實作的直譯器,另外有用 WebAssembly 技術跟 Pyodide 與 CPython 結合的 PyScript ,所以標準程式庫 (standard library) 或其他第三方程式庫 (third-party library) 都可以用 PyScript 執行,相對 Brython 不能執行第三方程式庫,但用來執行我們自己開發的程式, Brython 的執行速度目前優於 PyScript 。

JavaScript 最初目的是用來替 HTML 文件增加互動性,在瀏覽器大戰之後,由 W3C 制定 DOM 標準, DOM 為 Document Object Model 的頭字母縮寫詞,,中文稱之為文件物件模型 (document object model) ,各家瀏覽器平台共同均支持這套標準,然後用 JavaScript 操控 DOM ,若以上一個單元的 arricle_demo2.html 來看,其 DOM 結構如下

document - <html>
             - <head>
                 - <meta>
                 - <title>
                 - <link>
             - <body>
                 - <ariticle>
                     - <header>
                         - <h1>
                         - <time>
                     - <p>
                     - <p>
                     - <p>
                 - <footer>

最上層是 document 物件 (object) ,其下每個 HTML 元素 (element) 都是物件,簡單說,利用 document 取得像是 <p> 元素的物件,就可以進行 <p> 的相關設定,像是文字文內容、樣式等等,換句話說,就是可以用程式創建元素或是設定動態效果。

DOM 標準現在是由另一個組織 WHATWG 維護。

這裡我們繼續改寫 article_demo2.htmlarticle_demo3.html 作為示範

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<!DOCTYPE html>
<html>
<head>
    <!-- 設定網頁編碼 -->
    <meta charset="utf-8">
    <!-- 設定標題列文字 -->
    <title>Brython 範例</title>
    <!-- 引入 Brython 程式庫 -->
    <script src="https://cdn.jsdelivr.net/npm/brython@3/brython.min.js">
    </script>
    <script src="https://cdn.jsdelivr.net/npm/brython@3/brython_stdlib.js">
    </script>
    <!-- Python 程式 -->
    <script type="text/python">
# 從 Brython 引入 document 並以 doc 為別名
from browser import document as doc

# 按下 Hello 按鈕的事件
def say_hello(event):
    # 計數變數
    i = 0
    # 利用迴圈選取 class 屬性設定為 content 的網頁元素
    for p in doc.select(".content"):
        # 依計數變數設定網頁元素文字內容
        match i:
            case 0:
                p.textContent = "Hello!"
            case 1:
                p.textContent = "Hello Again!"
            case 2:
                p.textContent = "Hi! Hi! Hi!"
            case 3:
                p.textContent = "Hi! Hi! Hi! Again!"
        # 計數變數加一
        i += 1

# 點擊文章段落設定樣式
def add_style(event):
    # 依 id 屬性設定樣式
    doc["body"].style.color = "white"
    doc["body"].style.backgroundColor = "black"
    doc["header"].style.textAlign = "right"
    doc["header"].style.backgroundColor = "#222"
    doc["header"].style.padding = "1em"
    doc["h1"].style.textAlign = "center"
    doc["h1"].style.padding = "0.8em"
    doc["h1"].style.marginLeft = "0.8em"
    doc["h1"].style.marginRight = "0.8em"
    doc["h1"].style.border = "outset #f33"
    doc["time"].style.fontStyle = "italic"
    doc["footer"].style.textAlign = "center"
    doc["footer"].style.padding = "0.5em"
    doc["footer"].style.backgroundColor = "#222"
    # 設定所有 <p> 元素
    for p in doc.select("p"):
        p.style.border = "thick double #32a1ce"
    # 設定所有 class 屬性設定為 content 的元素
    for p in doc.select(".content"):
        p.style.padding = "1em"
        p.style.margin = "2em"
    # 依 id 屬性設定樣式
    doc["copyright"].style.padding = "0.6em"
    doc["copyright"].style.marginLeft = "1.4em"
    doc["copyright"].style.marginRight = "1.4em"
    doc["copyright"].style.border = "outset #ccc"

# 按鈕註冊事件
doc["hello"].bind("click", say_hello)
# <p> 元素註冊事件
for p in doc.select(".content"):
    p.bind("click", add_style)
    </script>
</head>

<body id="body" onload="brython()">
    <!-- 文章元素 -->
    <article>
        <!-- 文章置頂標題區域 -->
        <header id="header">
            <!-- 文章標題 -->
            <h1 id="h1">Brython 範例</h1>
            <!-- 文章時間 -->
            <time id="time">25.07.2023</time>
        </header>
        <p class="content">
            <button id="hello" type="button">Hello</button>
        </p>
        <!-- 文章段落 -->
        <p id="p1" class="content">段落內容一段落內容一段落內容一段落內容一<br />
           段落內容一段落內容一段落內容一段落內容一<br />
           段落內容一段落內容一段落內容一段落內容一<br />
           段落內容一段落內容一段落內容一段落內容一</p>
        <p id="p2" class="content">段落內容二段落內容二段落內容二段落內容二<br />
           段落內容二段落內容二段落內容二段落內容二<br />
           段落內容二段落內容二段落內容二段落內容二<br />
           段落內容二段落內容二段落內容二段落內容二</p>
        <p id="p3" class="content">段落內容三段落內容三段落內容三段落內容三<br />
           段落內容三段落內容三段落內容三段落內容三<br />
           段落內容三段落內容三段落內容三段落內容三<br />
           段落內容三段落內容三段落內容三段落內容三</p>
    </article>
    <!-- 頁尾資訊區域 -->
    <footer id="footer">
        <!-- 版權訊息 -->
        <p id="copyright">© 2023 PYDOING</p>
    </footer>
</body>
</html>

<!-- 檔名: article_demo3.html
     說明:《Python入門指南》的範例程式
     網站: http://kaiching.org
     作者: 張凱慶
     時間: 2023 年 7 月  -->

使用 Brython 要先用 <script> 元素引入 Brython 的 JavaScript 檔案,也就是第 9 行到第 12 行的範圍

  9
 10
 11
 12
    <script src="https://cdn.jsdelivr.net/npm/brython@3/brython.min.js">
    </script>
    <script src="https://cdn.jsdelivr.net/npm/brython@3/brython_stdlib.js">
    </script>

下面第 14 行開始就是 Python 程式,一直到第 71 行,有點多,我們先跳到第 75 的 <body> 標籤

 75
<body id="body" onload="brython()">

這裡 <body> 設定了 id 屬性 (attribute) ,這是因為 Brython 中透過 id 屬性直接設定元素會比較方便,所以底下很多會被 Python 程式設定的元素都添加了 id 屬性。除了 id 屬性之外, <body> 也增加設定 onload() 屬性, onload() 是指當這個 HTML 檔案被瀏覽器載入後去執行的 JavaScript 程式,這個 Brython() 就是定義在上面 Brython 的 JavaScript 檔案中。

然後底下新增一個 <p> 元素, <p> 元素中加入一個 <button> 元素

 85
 86
 88
        <p class="content">
            <bu tton id="hello" type="button">Hello</button>
        </p>

這裡 <button>type 屬性設定為 "button" ,這是讓這個 <button> 作為一個按鈕,這個按鈕預計會跟上面的一個 Python 函數 (function) 連結,讓使用者按下按鈕產生互動功能。

接下來我們回到 Python 程式來看, Brython 在 HTML 檔案寫 Python 程式有兩種方式,其中一便是這個直接寫在 <script> 標籤 (tag) 中,注意 <script> 標籤 type 屬性必須設定為 "text/python"

 14
    <script type="text/python">

實際開發 Brython 程式,同步打開瀏覽器的開發人員工具對除錯會比較有幫助。

然後我們先看到寫 Brython 的基礎步驟,注意此步驟是以設計函數為主

  1. 引入 document
  2. 視需要定義全域變數;
  3. 定義處理事件的函數;
  4. 向視窗元件註冊處理事件的函數;

簡言之,首先是要引入 document ,繼續看到第 16 行,這裡要從 broswer 引入 document ,然後將 document 改名 doc

 16
from browser import document as doc

接下來視需要定義全域變數 (global variable) ,此例不需要,所以直接定義處理事件的函數,首先看到 say_hello() ,注意處理事件的函數習慣上會以 event 當參數 (parameter)

 19
 21
 23
 25
 26
 27
 28
 29
 30
 31
 32
 33
 35
def say_hello(event):
    i = 0
    for p in doc.select(".content"):
        match i:
            case 0:
                p.textContent = "Hello!"
            case 1:
                p.textContent = "Hello Again!"
            case 2:
                p.textContent = "Hi! Hi! Hi!"
            case 3:
                p.textContent = "Hi! Hi! Hi! Again!"
        i += 1

say_hello() 的主要用途是在使用者按下按鈕後,重新設定網頁中 <p> 元素的文字,由於 article_demo3.html 中有 4 個 <p> 元素,因此要先定義計數變數 (variable) i ,然後在 for 迴圈 (loop) 。中依序取得 class 屬性設定為 content 的元素物件

 23
    for p in doc.select(".content"):

doc.select(".content") 回傳 HTML 元素中 class 屬性設定為 content 的串列 (list) ,所以可以放在 for 迴圈依序取得每個 HTML 元素物件,此例由於只有 4 個 <p>class 屬性被設定為 content ,因此會依序取得這 4 個 <p> 的 HTML 元素物件。

接下來用 match-case 依計數變數 i 分別設定 4 個 <p> 元素的文字內容

 25
 26
 27
 28
 29
 30
 31
 32
 33
        match i:
            case 0:
                p.textContent = "Hello!"
            case 1:
                p.textContent = "Hello Again!"
            case 2:
                p.textContent = "Hi! Hi! Hi!"
            case 3:
                p.textContent = "Hi! Hi! Hi! Again!"

最後計數變數 i 要遞增

 35
        i += 1

繼續看到 add_style() ,這是設定樣式的函數

 38
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 55
 56
 58
 59
 60
 62
 63
 64
 65
def add_style(event):
    doc["body"].style.color = "white"
    doc["body"].style.backgroundColor = "black"
    doc["header"].style.textAlign = "right"
    doc["header"].style.backgroundColor = "#222"
    doc["header"].style.padding = "1em"
    doc["h1"].style.textAlign = "center"
    doc["h1"].style.padding = "0.8em"
    doc["h1"].style.marginLeft = "0.8em"
    doc["h1"].style.marginRight = "0.8em"
    doc["h1"].style.border = "outset #f33"
    doc["time"].style.fontStyle = "italic"
    doc["footer"].style.textAlign = "center"
    doc["footer"].style.padding = "0.5em"
    doc["footer"].style.backgroundColor = "#222"
    for p in doc.select("p"):
        p.style.border = "thick double #32a1ce"
    for p in doc.select(".content"):
        p.style.padding = "1em"
        p.style.margin = "2em"
    doc["copyright"].style.padding = "0.6em"
    doc["copyright"].style.marginLeft = "1.4em"
    doc["copyright"].style.marginRight = "1.4em"
    doc["copyright"].style.border = "outset #ccc"

其中,像是直接由 doc 利用中括弧,中括弧中放入 id 屬性可以取得該元素,因此這是個簡單設定 HTML 元素的方式,例如第 41 行是把 <body> 的背景顏色設定為黑色

 41
    doc["body"].style.backgroundColor = "black"

背景顏色的 CSS 屬性為 background-color ,前後都為小寫英文單字,中間用連接線連接兩個單字,這裡用 Python 程式的屬性為 backgroundColor ,為名稱相同的小寫駝峰型,然後注意設定值要用字串 (string) 。

接下來看到第 55 行

 55
    for p in doc.select("p"):

doc.select("p") 會回傳所有 <p> 的元素物件串列,所以這裡是用來設定所有 <p> 元素的樣式,同樣在第 58 行

 58
    for p in doc.select(".content"):

doc.select(".content") 再次取得 class 屬性設定為 content 的元素物件串列,這是因為 class 屬性的 <p> 元素要額外設定樣式,注意 add_style() 的最後 4 行

 62
 63
 64
 65
    doc["copyright"].style.padding = "0.6em"
    doc["copyright"].style.marginLeft = "1.4em"
    doc["copyright"].style.marginRight = "1.4em"
    doc["copyright"].style.border = "outset #ccc"

本來用 id 屬性設定樣式在兩個 for 迴圈之前的位置,但由於 copyright<p> 元素 ,因此這裡由於執行順序,如果把 copyright 放在兩個 for 迴圈之前設定的話, for 迴圈會覆寫 copyright 的設定,所以要把 copyright 放到最後。

底下則是第 4 步驟向視窗元素註冊事件

 68
 70
 71
doc["hello"].bind("click", say_hello)
for p in doc.select(".content"):
    p.bind("click", add_style)

利用元素物件的 bind() 方法設定事件,這裡第一個參數 "click" 是點擊事件,然後第二個參數就是處理事件的函數,注意 <p> 元素的部分是每個 <p> 元素都適用,因此只要點擊 <p> 元素的範圍就會執行 add_style() 函數。

接下來用 Google Chrome 開啟 ariticle_demo3.html ,如下圖

點擊 Hello 按鈕就會同時改文字內容與改整個樣式

不是 Hello 按鈕只有註冊 say_hello() 嗎?怎麼同時執行 add_style() 呢?這是因為 Hello 按鈕在 <p> 元素中,只要點擊 <p> 元素就會執行 add_style() ,因此點擊 Hello 按鈕就會執行 say_hello()add_style()

使用 Brython 的第二種方式為引入 Python 程式檔案,這種方式需要在伺服器 (server) 使用,我們會在下一個單元利用 Python 內建的伺服器介紹如何引入 Python 程式檔案。

中英文術語對照
屬性attribute
瀏覽器browser
文件物件模型document object model
元素element
函數function
全域變數global variable
直譯器interpreter
串列list
迴圈loop
物件object
參數parameter
伺服器server
字串string
標準程式庫standard library
標籤tag
第三方程式庫third-party library
變數variable
重點整理
1. Brython 、 Pyodide 、 PyScript 都是可以在網頁執行 Python 程式的 Python 直譯器,目前 Brython 的執行速度較快。
2. DOM 為網頁中操作 HTML 元素物件的文件物件模型。
3. 寫 Brython 程式的基礎步驟包括引入 document 、定義全域變數、定義處理事件的函數、註冊處理事件的函數等。
問題與討論
1. Brython 、 Pyodide 跟 PyScript 三者技術有何差別?
2. 為什麼要用 DOM ?這有什麼好處?
練習
1. 寫一個 HTML 檔案 exercise4101.html ,利用 Brython 寫一個按下按鈕就將計數遞增的網頁。 參考程式碼
2. 寫一個 HTML 檔案 exercise4102.html ,利用 Brython 寫一個輸入框輸入文字就顯示按下哪個鍵盤按鍵的網頁。 參考程式碼
3. 承上題,將新 HTML 檔案寫在 exercise4103.html 中,輸入框改成只能限制輸字數字。 參考程式碼
4. 寫一個 HTML 檔案 exercise4104.html ,利用 Brython 寫一個按下按鈕就能隱藏段落的網頁。 參考程式碼
5. 承上題,將新 HTML 檔案寫在 exercise4105.html 中,隱藏段落文字後同時改變按鈕文字。 參考程式碼
6. 寫一個 HTML 檔案 exercise4106.html ,利用 Brython 寫一個按下按鈕就顯示現在時間的網頁。 參考程式碼
7. 承上題,將新 HTML 檔案寫在 exercise4107.html 中,將時間格式化以時、分、秒顯示。 參考程式碼
8. 承上題,將新 HTML 檔案寫在 exercise4108.html 中,改成顯示逐秒推進的電子鐘。 參考程式碼
9. 承上題,將新 HTML 檔案寫在 exercise4109.html 中,將個位數字之前補上 0參考程式碼
10. 承上題,將新 HTML 檔案寫在 exercise4110.html 中,加入按鈕用提示視窗顯示現在時間。 參考程式碼

上一頁 單元 40 - CSS 3 規則簡介
回 Python 入門指南 5.0 首頁
下一頁 單元 42 - 啟動伺服器與加入 encrypt.py
回 Python 教材首頁
回程式語言教材首頁