Python 入門指南 5.0
單元 41 - Brython 與 JavaScript
如果要在瀏覽器 (browser) 中執行 Python 程式,有數種方案,其中之一便是 Brython 直譯器 (interpreter)
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.html 成 article_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 的基礎步驟,注意此步驟是以設計函數為主
- 引入 document ;
- 視需要定義全域變數;
- 定義處理事件的函數;
- 向視窗元件註冊處理事件的函數;
簡言之,首先是要引入 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 中,加入按鈕用提示視窗顯示現在時間。 參考程式碼 |