15 KiB
在 BBC micro:bit 上學 Python
作:Alan Wang, Feb 2021
本指南採用 GNU 3.0 通用公共授權
關於本指南
這份指南不是替兒童而寫,而是有興趣使用 BBC micro:bit 開發板為媒介來學習 Python 語言的成人或青少年。
現有的書籍或網路教材,在談及 micro:bit 的 Python 教學時,多半著重在 micro:bit 的功能和相關的範例,卻鮮少深入 Python 的語言特色。這份指南的目的是借用 micro:bit 的硬體特色來介紹 Python 的重要概念,好讓學習者將來能將之應用在正式的 Python 程式開發。
至於 micro:bit 是什麼、為什麼現在要學 Python,這些在網路上已經有非常多討論,這裡就不再贅述。
閱讀本指南所需的準備
本指南會使用 2020 年 10 月後上市的新版 micro:bit V2,但大部分程式碼也適用於 V1。筆者推薦使用 V2,因為價格相近但規格更佳,有更多記憶體執行 Python 直譯器。
你也需要 micro USB 線以及一台有 Google Chrome 瀏覽器的電腦,就這樣。不須安裝其他東西。
Python 編輯器
打開 https://python.microbit.org/v/2 :
這是 micro:bit 官方提供的線上版瀏覽器,雖然簡單,但就本指南的目的來說夠用了。後面有機會的話,筆者會在談如何使用其他 Python 編輯器。
畫面上方有五個大按鈕:
- Download/Flash (下載/燒錄)
- Connect/Disconnect (連線/解除連線)
- Load/Save (上載檔案/存檔)
- Open Serial/Close Serial (打開 REPL/關閉 REPL)
- Help (線上說明)
按鈕的內容會因與 micro:bit 連線與否而有所不同。
與 micro:bit 連線和燒錄第一支程式/韌體
Python 是直譯式語言,這表示程式運作的地方必須裝有直譯器。幸好,官方編輯器簡化了這個流程:你從編輯器下載的 .hex 程式檔中,有一部分會包著 micro:bit 用的 Python 韌體,剩下的則是你寫的程式(又稱草稿碼)。即使你的 micro:bit 之前並沒有安裝 Python 韌體,只要燒錄一次程式就會自動完成韌體安裝。這也使得第一次燒錄──或者在使用過其他語言後重新燒錄──Python 會花較久的時間。
micro:bit 使用的 Python 實際上是 Micropython──設計給記憶體有限的開發版的 Python 版本。Micropython 本身就有好幾種版本,針對不同類型的開發板而作,不過它們都是是以 Python 3.4 為基礎。所以,Python 3.4 擁有的核心語言功能,在 micro:bit 都是可以使用的。
那麼,我們就來燒錄一支程式,直接用官方編輯器一打開就出現的程式即可:
# Add your Python code here. E.g.
from microbit import *
while True:
display.scroll('Hello, World!')
display.show(Image.HEART)
sleep(2000)
將 micro:bit 接上電腦,然後按畫面中的 Connect,讓 Chrome 瀏覽器跟你的裝置連線:
連線後按 Flash。等待程式燒錄作業結束。
較近期的 micro:bit 的韌體支援 WebUSB,讓瀏覽器能跟 USB 裝置連線,這麼一來編輯器就能直接下載程式到 micro:bit 上。現在市售的 micro:bit 應該都有新韌體了;但若你沒辦法連線,請用 Download 下載 .hex 檔後複製/貼上到 micro:bit 的 USB 磁碟區。
REPL 互動介面
想學 Python,就不可不知 REPL(Read-Eval-Print Loop,讀取-求值-輸出循環)。簡單說,這是 Python 直譯器的互動介面,能讓開發者用來做簡單的測試、探索 Python 的功能、讀取程式的回應等等,不管在電腦或開發板上,對程式開發者來說都是很實用的工具。因此後面在講解 Python 入門時,我們會先在 REPL 下來示範。
在 micro:bit 連線的情況下,點編輯器的 Open Serial,然後點按鈕下方右側的「Send CTRL-C for REPL」或直接在鍵盤上按 Ctrl+C。你應該會看到以下畫面:
這幾行文字
MicroPython v1.13 on 2020-12-21; micro:bit v2.0.0-beta.3 with nRF52833
Type "help()" for more information.
>>>
就是 REPL 的提示,類似 Windows 命令提示字元或 Unix 的終端機。它告訴我們 Python 直譯器的版本,並等待我們輸入指令。
現在於 >>> 後面輸入 1 + 2,並按 Enter:
>>> 1 + 2
3
可以發現 Python 直譯器解讀了你輸入的程式,並自動算出答案。
現在,照 REPL 提示所說的輸入 help():
>>> help()
Welcome to MicroPython on the micro:bit!
Try these commands:
display.scroll('Hello')
running_time()
sleep(1000)
button_a.is_pressed()
What do these commands do? Can you improve them? HINT: use the up and down
arrow keys to get your command history. Press the TAB key to auto-complete
unfinished words (so 'di' becomes 'display' after you press TAB). These
tricks save a lot of typing and look cool!
Explore:
Type 'help(something)' to find out about it. Type 'dir(something)' to see what
it can do. Type 'dir()' to see what stuff is available. For goodness sake,
don't type 'import this'.
Control commands:
CTRL-C -- stop a running program
CTRL-D -- on a blank line, do a soft reset of the micro:bit
CTRL-E -- enter paste mode, turning off auto-indent
For a list of available modules, type help('modules')
For more information about Python, visit: http://python.org/
To find out about MicroPython, visit: http://micropython.org/
Python/micro:bit documentation is here: https://microbit-micropython.readthedocs.io/
這是 micro:bit 的 MicroPython 內建的訊息,包含一些簡單的指引。注意到這兒也提到了 Ctrl+C 和 Ctrl+D,前者是用來中斷 micro:bit 目前執行的程式(程式執行時不會出現前面的提示),而 Ctrl+D 是用來強迫 micro:bit 重開機,以便重新測試程式。
現在試試 help("modules"):
>>> help("modules")
__main__ math os ucollections
audio microbit radio urandom
builtins micropython speech ustruct
gc music sys utime
machine neopixel uarray
Plus any modules on the filesystem
現在 REPL 列出了 MicroPython 中的所有模組(module),每一個都代表一些特定的程式功能。之後我們會看到它們是什麼,以及要如何使用。
Python 基礎:運算式
在 Python 中,程式碼可分為兩類:陳述(statement)與運算式(expression)。這兩者都會執行某個功能,但運算式本身也代表某個值,比如前面的 1 + 2 會得到 3。
運算式可以放在其他運算式中,而 Python 有很多會傳回值的東西都可以當成運算式的一部分。最簡單的運算式由值和**運算元(operator)**構成,例如上面的算式有數字 1 和 2,中間是加號運算元(+)。
我們先來看 Python 常用的運算元:
| 功能 | Python 運算元 |
|---|---|
| 加 | + |
| 減 | - |
| 乘 | * |
| 除 | / |
| 整數除 | // |
| 餘數除 | % |
| 次方 | ** |
| 小括號 | () |
在 REPL 中試試看:
>>> 2 + 3
5
>>> 2 - 3
-1
>>> 2 * 3
6
>>> 10 / 3
3.333333
>>> 10 // 3
3
>>> 10 % 3
1
>>> 2 ** 3
8
>>> 10 / (2 + 3)
2.0
數字與運算元之間一定要有空格嗎?不用,甚至打一堆空格都無所謂,但留一個空格是最美觀的。
Python 基礎:資料型別
前面的運算式,其實只使用到數字而已。而在 Python 中,不同的資料有不同的型別(type),而型別決定了資料(以及你對資料)能做哪些事。
Python 最基本的幾種型別為:
型別 | 範例 int (整數) | 42 float (浮點數) | 3.14159 str (字串) | "test" bool (布林值) | True/False NoneType (無值) | None
整數與浮點數
以上面的運算元來說,整數(無小數點)和浮點數(有小數點)是可以放在一起運算的,而取決於運算元,結果會是整數或浮點數。例如,整數用 / 相除會得到浮點數, 用 // 相除卻會得到整數。
如果用 // 時算式裡有浮點數呢?這樣就還是會得到浮點數:
>>> 10 // 3.0
3.0
字串
字串前後必須用雙引號 (") 或單引號 (') 括起來:
>>> "This is a string"
'This is a string'
Python 預設是用單引號,不過大多時候沒有差別,取決於個人習慣。單引號看起來比較簡潔,但雙引號在多數語言是表示字串的通用用法。其實 Python 還有幾種其他代表字串的變形形式,不過一開始不需要知道。
當然,字串內容可以打任何語言。不過 micro:bit 的網頁編輯器打中文有點容易亂跳,所以還是打英文吧。
字串和整數、浮點數最大的差別,在於它們不能放在一起運算:
>>> 1 + 1
2
>>> "1" + "1"
'11'
>>> 1 + "1"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported types for __add__: 'int', 'str'
>>> "1" + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't convert 'int' object to str implicitly
當你把數字寫在用 " 或 ' 括起來的字串中,那些數字就是字串而不是數字了。當你對兩個字串使用 + 號,這些字串只會直接連在一起。
這顯示了 + 運算元對數值和字串的意義是不一樣的,所以你嘗試執行 1 + "1" 或 "1" + 1 就產生錯誤了。之後我們會再談到怎麼藉由轉換資料型別的方式,好讓它們可以一起運算。
執行遇到問題時,Python 直譯器會顯示錯誤訊息,告訴你錯誤發生的行數(由於我們在使用 REPL 介面,所以位置是第 1 行)以及原因(TypeError,型別錯誤)。這時我們就可以根據這些訊息來嘗試排解錯誤(除錯)。
布林值
布林值(Boolean)就是「是/否」或「真/假」的二元結果,在 Python 中這兩個值分別是 True 和 False(第一個字一定大寫),主要用在邏輯判斷用途,這些後面會再探討。
Python 的布林值同時也是一種特殊的數值,0 和 None 就等於 False,除此以外的其他任何數字則等於 True。
None
None 是個特殊的值,代表沒有值的意思。你用到它的機會非常少,但有些 Python 功能會把它當成沒有值時的預設值。
Python 基礎概念:變數
宣告變數
**變數(variable)**是用來在程式中記住資料的方法,就像是解數學方程式時的 X 或 Y。變數會有一個值,而這個值是可以隨意改變的。
在 Python 中,若要宣告(define)一個新變數,辦法是用 =(指派運算元)號指定或賦予一個值給某個名稱:
>>> a = 1
此舉建立了名為 a 的變數,值為整數 1。
>>> a
1
>>> a + 3
4
如果用 = 指定新的值給 a,它的值會隨之改變:
>>> a = 2
>>> a
2
>>> a * 3
6
你甚至可以這樣寫:
>>> a = 1
>>> a
1
>>> a = a + 4
>>> a
5
請記住 = 號不是數學上的等於,而是將其右邊的值指定給左邊。一開始 a 的值為 1,然後我們指定新的值 a + 4(也就是 5) 給 a。因此 = 完成運算後,a 就變成 5 了。
最後,來按編輯器的 Close serial 關閉 REPL 介面,然後重開(記得按 Ctrl+C),並再次輸入 a 查詢其值:
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' isn't defined
現在產生了錯誤,說名稱 a 不存在。這是因為重開 REPL 後,變數 a 就在記憶體裡消失了。
後面在執行完整的程式時,每次重新執行的效果就等同於重新啟動 REPL,所以你必須在程式裡正確的地方宣告變數,後面的程式使用它時才不會遇到問題。
Python 變數是個「路牌」
現在要來講一個比較難懂、但仍然非常重要的觀念,因為這能解釋 Python 變數在賦值時的極大彈性。
在許多程式語言中,變數是個容器,實際上就是記憶體裡的一個空間,然後把值存在那裡面。既然是事先指定的空間,你只能用它來儲存特定類型的資料。
但 Python 的做法卻剛好相反:值本身已經存在於記憶體中某處了,你所做的其實是做一個「路牌」來指向它。
以 a = 1 為例,整數 1 其實已經存在於記憶體內,我們只是新增一個名稱 a 來代表它。而當你執行 a = 2 時,路牌 a 會指向記憶體中的整數 2。這時整數 1 仍然存在,只是除了直接寫出 1 以外,我們沒有別的方式能引用 1。換言之,變數可以指向任何資料,就算途中改變資料的類型也無謂:
>>> a = 1
>>> a
1
>>> a = "test"
>>> a
'test'
上面 a 一開始是整數,後來變成字串了。
當然,整數 1 和 2 等等是特例,它們是 Python 一開始就擁有的資料。有些資料是由使用者在執行程式期間建立的。
Python 變數能指向各式各樣的東西,這使得熟悉 Python 的人可以用變數來做一些很神奇的事。當然對初學者來說,這有時也會產生一些令人困惑的現象。
這裡先來舉個例子。在 Python 中,函式 print 能用來印出資料:
>>> print("test")
'test'
稍後我們會解釋什麼是函式。目前你只需要知道,函式呼叫時會在名稱後面加上 ()。
在 REPL 模式下,它會自動將運算式的值印出來,可是稍後在執行正式程式時就不會這樣了。反而,你得使用 print() 將訊息印在 REPL 介面內,好讓我們知道程式的執行狀況等等。
print 是 Python 事先定義好的東西。要是我們指定一個新的值給 print 這個名稱,會發生什麼事?
>>> print = "this is a string"
>>> print
'this is a string'
>>> print("test")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object isn't callable
>>>
現在 print 這個路牌指向字串 'this is a string',而不是原本能印出東西的功能了。當我們嘗試加上 () 來呼叫它時,Python 直譯器回報錯誤,說你不能把字串當成函式呼叫。(你可以藉由重開 REPL 讓 print 函式恢復正常。)
可以想見,若你無意間使用 Python 內建的名稱,並指定新的值給它,這可能會引起一些困擾。
變數的命名規則
Python 變數的命名非常自由,除了不能用數字開頭以外,可以隨意混搭字母、數字和底線:
>>> _Number_01 = 42
>>> _Number_01
42
Python 變數名稱也支援 Unicode,因此使用中文、日文或各國語言都是可行的。使用非 Unicode 特殊字元會產生錯誤。不過,一般習慣上還是會以英數為主,而且最好能清楚反映變數本身的用途。
最後就是和前面提過的一樣,最好不要使用 Python 各種內建功能的名稱,否則你會意外覆蓋掉原有的功能。若真的非用不可,可以試著在該名稱前或後加上底線,如此一來就會被 Python 視為不同的名稱了。
(持續寫作中...)



