2021年7月31日 星期六

Thonny 離線安裝 Python 套件的問題

因為微軟將於 2022 年 6 月 15 日終止對 IE 的更新支援, 之前幫公司寫的網頁版程式屆時就無法使用了 (因為使用了一些 ActiveX 元件), 所以必須超前佈署, 改用 Python 來寫個替代的作業輔助軟體. 但由於資安限制, 無法用 pip 直接安裝所需套件, 必須下載 whl 檔進行離線安裝, 忙了兩天終於在昨天搞定了. 

我會用到的主要套件包括用來進行 SSH 連線的 paramiko, 還有網路爬蟲用的 requests, BeautifulSoup, 以及資料科學四大天王, Numpy, Pandas, Matplotlib, 與 Scipy 等等. 由於離線安裝要考慮到相依套件問題, 例如 Pandas 並不是只下載 Pandas 的 whl 檔就可以了, 還要先下載安裝完它的相依套件後才能順利安裝 Pandas. 

我打算使用 Thonny 來開發, 所以先安裝最新版 Thonny, 它目前內建的是 Python 3.7. 安裝程序則參考了下面這篇空大老師吳俊逸博士的文章 : 


我先到 PyPi 網站依序下載這些套件的 whl 檔, 但要先確認 Python 直譯器版本與 32(x86)/64(amd) 位元, 因為有些套件有分版本與位元數, 有些則沒有. 下錯 whl 檔安裝會失敗, 例如我的 Thonny 內建直譯器是 3.7 版, 下載非 3.7 版的 Numpy 就無法安裝, 同樣是 3.7 版, 下載 64 位元版也是不行, 而是要 32 位元版 : 





不過我照此程序安裝發現作者的這張列表寫得並不完全, 還有些其他相依套件漏列, 我依據安裝錯誤訊息下載闕漏套件來回用隨身碟複製到主機好幾次才完成整個常用套件離線安裝作業. 

安裝順序如下 : 

1. SSH 工具 (paramiko) :
    (1). pyasn1
    (2). bcrypt
    (3). PyNaCl
    (4). paramiko  

2. 網路爬蟲 : 
   (1). requests
   (2). urllib3  

3. 資料科學 :
   (1). numpy  
   (2). pyparsing
   (3). pytz
   (4). setuptools
   (5). six
   (6). python_dateutil
   (7). cycler
   (8). Pillow
   (9). kiwisolver
   (10). matplotlib  
   (11). pandas  

4. 科學計算 : 
   (1). mpmath 
   (2). sympy    
   (3). scipy    

5. 機器學習 (scikit-learn & tensorflow 2) : 
   (1). threadpoolctl 
   (2). joblib 
   (3). Scikit-learn   
   (4). termcolor
   (5). typing-extensions (3.7.4)
   (5). tensorflow

6. 自然語言處理 (SpaCy&NLTK) :
   (1). wasabi
   (2). catalogue
   (3). srsly 
   (4). packaging
   (5). typing-extensions (3.7.10)
   (6). pydantic
   (7). spacy-legacy
   (8). murmurhash
   (9). blis
   (10). cymem
   (11). preshed
   (12). thinc
   (13). colorama
   (14). click  (v7.1.2)
   (15). typer
   (16). tqdm
   (17). markupSafe
   (18). Jinja2
   (19). smart-open
   (20). pathy
   (21). idna
   (22). charset-normalizer
   (23). certifi
   (24). requests
   (25). Spacy   
   (26). regex
   (27). nltk   

先在可連網電腦下載如上的所有套件, 然後複製到目的地電腦上依序安裝即可. 注意, SpaCy 的相依套件 click 版本有限制, PyPi 上只提供最新 8 版, 必須到加州大學爾灣分校網站下載 7.1.2 版才能順利完成安裝 (click 是 typer 的相依檔案) :

# click‑7.1.2‑py3‑none‑any.whl

其次, 有些相依套件有版本要求, 有的不能太新, 例如 SpaCy 與 TensorFlow 都需要用到 typing-extensions 但 TensorFlow 的 typing-extensions 要求的是 3.7.4 版, 若安裝 Pypi 上最新的 3.10.0 版不能用, 要到加州大學爾灣分校網站下載 : 

# typing_extensions‑3.7.4.1‑py2‑none‑any.whl

2021年7月30日 星期五

好文 : Python 六大自然語言處理函式庫之比較 (from KDnuggets)

今天在 KDnuggets 看到一篇好文章 : 


此文對 Python 六大自然語言處理函式庫進行了評比 , 剛好我最近在研讀 NLP, 所以仔細看了全文, 對於這六大套件的優劣算是有了一個粗略的印象, 內容摘要翻譯整理如下 :
  1. 自然語言處理包含下列次領域 :
    (1). 語音辨識與生成 (speech recognition & generation)
    (2). 文本分析 (text analysis)
    (3). 情緒分析 (sentiment analysis)
    (4). 機器翻譯 (machine translation)
  2. 研究 NLP 除了需要數學與機器學習基礎外, 還要具有語言學概念. 
  3. NLP 函式庫讓文本的前置處理 (preprocessing) 大幅簡化, 使我們可以專心於機器學習模型之建立與超參數 (superparameter) 的微調. 
  4. Python NLP 六大函式庫 :
    (1). NLTK
    (2). SpaCy
    (3). Scikit-Learn
    (4). Gensim
    (5). Pattern
    (6). Polyglot
    其中 NLTK 是最知名且歷史悠久的 NLP 套件, 彈性大且支援最多語言, 但執行速度較慢, 學習曲線較陡, 且無神經網路模型. SpaCy 的處理速度最快, 易學易用屬於工業級產品, 但只支援 7 種語言. Scikit-Learn 提供廣泛的演算法來建立模型, 但前處理時無法使用神經網路, 需靠其他套件支援. Gensim 支持深度學習, 但主要是設計用來建立非監督式的文本模型, 且其工具無法支持完整的 NLP 作業管線 (pipeline). Pattern 提供了主要的 NLP 功能且內建網頁爬蟲 API, 但它主要是一個網頁探勘套件, 無法對某些特定的 NLP 作業進行優化. Polyglot 支援最多達 196 種語言, 但處理效能較慢且社群支持較少. 
  5. 目前最受歡迎的 NLP 套件是 NLTK 與 SpaCy, 兩者也互為主要的競爭者. NLTK 較偏向學術性, 使用者可測試其所提供的各種方法與演算法, 或混用它們. SpaCy 則對每個問題提供開箱即用 (out-of-box) 的單一解決方案, 所以也不需要去比較哪個方法較好. SpaCy 的優勢是其工業級的處理效能 (是 NLTK 的好幾倍), 可惜所支援的語言數目較少, 不過也在持續增加中. 因此在大部分的情況下建議採用 SpaCy, 如果想嘗試一些不同的做法也可考慮使用 NLTK 來做. 
我以前安裝完 NLTK 後做了簡單測試就放下了, 因為它所提供的方法有點多, 光是 tokenization (斷詞) 的函數就好幾個, 最近稍微看了一下 SpaCy 的用法, 發覺 SpaCy 乾脆俐落, 它不會提供一大堆函數來處理相同的問題, 而且內建的模型也考慮到語境 (context), 所以斷詞的結果非常正確, 所以我想先學學 SpaCy, 以後有需要再來玩看看 NLTK. 

2021年7月29日 星期四

打 AZ 疫苗

下午請公假到巨蛋打疫苗, 預約 3 點, 但我 2 點 45 就到了, 遵循指引填寫資料, 醫師問診, 到打完針不超過 10 分鐘, 休息 15 分鐘就可以離開了.




晚上在看倚天屠龍記時, 婷婷打電話來問感覺怎樣, 我說才 6 小時好像沒感覺哩, 聽說一般要 12 小時左右才會有反應. 現在已過了 9 小時, 普拿疼已備好, 預防半夜發燒頭痛. 


2021-07-30 補充 :

昨晚很好睡, 沒發燒也沒頭痛, 早上起來手臂施打處微痛. 但一整天上班感到累累的, 中午午睡還睡過頭. 早上同事看到我, 還問我怎麼跑來上班, 因為樓上同事前一天下午請公假打疫苗, 第二天早上又徵得主管同意請半天公假在家休息. 但我覺得又沒怎樣不舒服, 況且還有 Python 離線安裝沒做完, 幹嘛找主管再請公假呢? 雖然沒有像水某打第一劑那樣頭痛昏睡一天半, 但腰痠倦怠倒是有的, 打之前忐忑不安, 備好的普拿疼也用不到. 

2021年7月28日 星期三

樹莓派架站 (十二) : 申請中華電信 ADSL/光世代固定 IP

由於湘雲老師要八月底才回學校, 所以想利用這段時間試看看讓網站用光世代連上 Internet. 中華電信 ADSL/光世代雖然是浮動 IP, 但提供用戶線上設定一個固定 IP + 7 個浮動 IP, 這對於有裝設上網型 VCR 監視器的用戶來說非常方便, 在監視器上設定用固定 IP 撥接帳號以 PPoE 方式撥接上網後即可在遠端連線此 IP 而存取監視器畫面, 當然也可利用此 IP 來連線網站伺服器. 

申請中華電信一個固定 IP 的網頁如下 :


點選 "Hinet 非固定制固定 IP" 按鈕 : 




按 "馬上登入" 鈕登入 Hinet 帳號 : 




輸入會員帳號 (HN 不要打, 只要輸入 HN 後面的數字即可) 與密碼, 按 "登入" 鈕 :




我鄉下家的設定範例 (非實際) 如下 : 



老張家的設定範例 (非實際) 結果頁面如下, 底下多了 PPoE 撥接方式的提醒 : 




亦即在使用 PPoE 撥接時, 固定 IP 登入帳號需為 xxxxxxxx@ip.hinet.net, 這個很重要, 如果是將固定 IP 綁定到 VCR 監視器, 也是要用此帳號 (但一戶只能有一台設備使用此固定 IP 帳號).

參考:


2021年7月27日 星期二

露天購買藍芽耳機與榨汁機

今天在露天看到 145 元的藍芽耳機, 想說最近常上線上課程所以就買了一組, 因為免運要 177 以上, 所以加買一個菁菁做蛋糕要用的檸檬榨汁器 :





免運合計 183 元. 

2021-07-30 補充 :

貨品今天到貨, 耳機試用音質還可以. 藍芽連線名稱為 i9s, 說明書操作方法摘要如下 : 
  1. 開機 : 長按按鍵 3 秒顯示燈閃爍藍燈後即開機. 
  2. 配對 : 開啟手機或電腦藍芽搜尋 i9s 名稱的藍芽裝置進行配對, 雙擊任一耳機的按鍵即自行配對, 成功後會間隔 5 秒閃爍藍燈. 
  3. 關機 : 長按按鍵 3 秒顯示燈閃紅燈三次後即關機, 在左右耳機配對使用時, 只要在其中一個操作關機, 另一支耳機將自動隨同關機. 
  4. 聽音樂 : 音樂播放中按一下按鍵將暫停播放, 再按一下則繼續. 長按 2 秒跳下一曲. 
  5. 電話 : 藍芽連線中可撥打電話, 這時音樂會暫停, 掛斷後會自動繼續播放. 聽音樂時接到來電會語音提示來電號碼, 按一下按鍵即暫停音樂接聽, 掛斷後音樂會繼續播放, 雙擊按鍵將回撥電話. 來電時若長按按鍵則為拒接, 通話中按一下可掛斷電話.  
  6. 充電 : 將耳機放進充電倉, 充電時耳機紅燈長亮, 充電口閃紅燈, 充飽電後兩個都熄滅. 

2021年7月26日 星期一

2021 年第 30 周記事

上週二預約了打 AZ 疫苗, 原本想要周五下午請假去打, 但因周四周五兩天要上課, 所以預約 7/29 週四下午到巨蛋去打. 

週日下午打理完晚餐備菜後準備採收龍眼, 但一直在下雨, 直到傍晚間歇暫停時, 趕緊抬出高鋁梯, 請爸在底下拿竹籃竿接果, 總共採了六袋, 一袋給岳父, 一袋給元順他爸, 感謝他常拿拔樂過來, 一袋給琪大師, 因上回來鄉下辦桌, 拿錢又不收, 只好用龍眼略表心意. 另外給阿中一袋, 留給爸一袋, 其餘自用. 為了周末繼續採收靠田那邊以及較高的果子, 晚上也在露天買了一支採果器. 

本週實驗了了道菜, 週日中午試做咕咾肉, 雖然是用端午回鍋肉製作, 但滿滿一大盤也都銷完, 嗯, 非常有成就感. 晚上則試做紅燒吳郭魚, 比以前竿間要有味道, 但煎魚時用油太多是一敗筆 (就中午油炸咕咾肉丁剩下的), 下次還是像乾煎那樣的油量即可. 

本周我都在埋首讀 tkinter 文件並進行測試, 我以為可以快速搞定它, 實際上做起來並不容易. 我現在是採取雙主題學習, 先讀兩三天 Numpy, 疲乏之後就換 tkinter, 如此一再反覆就可以避免因為單一科目學太久太過單調而失去動力問題. 

露天購買 Type C 磁吸頭

上回幫爸的小米 Poco M3 手機買的磁吸頭到貨才發現勾選到安卓, 應該選 Type C 才對, 所以趁著露天奧運優惠上網再添購磁吸頭 (線已有) :





奇怪, 可能是沒達到門檻, 免運券有選但沒有用, 所以優惠券 60 元好底運費. 

露天購買高枝剪+摘果器

晒穀場邊的龍眼本周開始採收, 週日下午我拿高梯架在樹下, 爸拿裝果袋竹竿接取剪下來的龍眼, 趁著陣雨的間歇空檔, 順利將西側的龍眼大致採完, 但更高的就需要買個採果竿才行, 今晚 10 點露天剛好有奧運活動, 輸入 TEFIGHT 省兩百, 這次優惠終於被我搶到了! 





除了奧運優惠券省兩百, 還有免運券, 原價 1427 實付 1227 元, 哈哈. 

2021年7月25日 星期日

料理實驗 : 試做咕咾肉

鄉下家的冰箱因為放波羅蜜而幾乎塞滿必須清一清, 找出端午節拜拜的豬肉, 以前都用來做紅燒肉, 但這已吃膩了. 不如就先拿來試做咕咾肉, 先學個經驗, 下周姊姊回來再買新鮮的豬後腿肉來做. 作法參考柚子媽媽的食譜 :







雖然是回鍋肉, 我還是照食譜用刀背把每片肉都打過才切, 結果肉好像比食譜的還多, 起鍋前嘗一下味道發覺甜度不夠, 所以又補了半匙糖才對味. 滿滿一大盤居然吃到只剩下一點點, 不過還是夠裝爸的便當兩個啦! 

自評有 90 分, 這是我第一次做油炸實驗, 算是很成功, 以前都覺得油炸要用掉很多油很浪費, 所以用到油炸的食譜我都跳過, 但其實用過的油都可以存下來炒菜, 一點都不會浪費. 不過下周做之前要把程序記牢, 才不會臨時抱佛腳, 尤其油鍋需掌握一下火候與時間. 

天瓏書局預購優惠

今天在天瓏看到兩本即將上市的好書, 預購有 75 與 79 折優惠 :


旗標在明儀 VIP 是 85 折,  能拿 75 折算是很優啦!

另外這本也不錯 : 


還沒決定要預購, 因最近買書買太兇, 先記錄下來, 或許上市後 momo 更優. 

2021年7月23日 星期五

Python 內建 GUI 模組 tkinter 測試 (五) : Label 元件

經過複習暖身後, 準備打鐵趁熱繼續 tkinter 的學習. 本篇測試 GUI 視窗應用程式中最常見的 Label 元件 (widget).

本系列之前的文章參考 :

Python 內建 GUI 模組 tkinter 測試 (一) : 建立視窗
Python 內建 GUI 模組 tkinter 測試 (二) : 對話框
Python 內建 GUI 模組 tkinter 測試 (三) : 版面管理員


1. Lebel (標籤) 元件 : 

Label 物件用來建立文字或圖片標籤, 主要功能是作為引導說明之用, 例如表單欄位名稱標示, 或顯示狀態結果等, 只能看而無法互動. 

其語法如下 :

Label(父容器, **參數列)   

參考 :


Label 元件常用參數如下表 : 


 Label 元件常用屬性 說明
 text 標籤文字內容, 多行文字可用 "\n" 跳行
 width 寬度 (單位=字元數)
 height 高度 (單位=字元數)
 bg/background 背景色, 可用標準顏色名稱字串例如 'blue' 或 "#0000ff"
 fg/foreground 前景色 (文字顏色), 可用標準顏色名稱字串例如 'blue' 或 "#0000ff"
 font 字型與尺寸 (px), 粗體 (bold) 或斜體 (italic), 底線或刪除線等 
 padx 元件與容器的水平間距 (px)
 pady 元件與容器的垂直間距 (px)
 textvariable 綁定標籤文字內容的 StringVar 物件之變數名稱
 bitmap 設定預設之 Bitmap 圖示作為標籤內容
 relief 外框型式, sunken  flat (預設), groove, raised, ridge, solid
 bd/borderwith 邊框寬度 (預設 1px)
 underline 設定第幾個字元加上底線 (0 起始, 預設 -1 都不加底線)
 justify 設定多行文字時的對齊方式, LEFT/CENTER (預設)/RIGHT
 anchor 設定錨定位置="e", "w", "s", "n", "es","en", "ws", "wn","center" (預設)
 compound 設定疊圖方式, TEXT/IMAGE/CENTER/TOP/BOTTOM/LEFT/RIGHT
 image 設定標籤文字疊圖時之 PhotoImage 圖片物件名稱
 wraplength 設定字串超過多少寬度 (單位 px) 換行


這些參數可以在建立 Label 物件時傳入, 也可以在建立物件後呼叫其 config() 方法設定. 其中最重要的是 text 參數, 用來設定標籤文字. 其次是 image 參數, 用來設定圖片標籤, 其值為一個 PhotoImage 物件, 可呼叫 tkinter 的 PhotoImage() 函數並傳入圖檔名稱 file 來建立此物件 :

image=tk.PhotoImage(file="myphoto.png")   

tkinter 僅支持四種圖片格式 :
  • gif
  • pgm
  • ppm
  • png (8.6 板以上)
如果要使用 jpg 或 bmp, 則需要用例如 Pillow 等套件進行轉換. 

除了可用 image 參數建立圖片標籤外, 也可以用 bitmap 參數設定位元圖示, 以下是在各作業系統都可用的通用 bitmap 名稱 (字串) :


 bitmap 位元圖示名稱 說明
 "error" 錯誤
 "hourglass" 沙漏
 "info" 訊息 (i)
 "questhead" 問號與人頭
 "question" 問號
 "warning" 驚嘆號
 "gray12" 淡灰階
 "gray25" 淺灰階
 "gray50" 次暗灰階
 "gray75" 暗灰階


注意, ttk 不支援 bitmap 參數, 只有 tk 可用 bitmap, 其次, bitmap 與 image 這兩個參數不能同時設定. 

Label 元件的常用方法如下表 : 


 Label 元件常用方法 說明
 config(**options) 設定物件屬性 options
 keys() 傳回 Label 物件的屬性串列 (元件共通方法)


其中 keys() 是所有 tkinter 元件都有的共通方法, 它會傳回物件的屬性串列, 如下例所示 : 


測試 1 : 比較 tk 與 ttk 的 Label 物件屬性 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()    
tk_label=tk.Label(win, text="Hello World!", bg="ivory")    # 建立 tk 的標籤文字物件
tk_label.pack()
ttk_label=ttk.Label(win, text="你是在說哈囉嗎?", background="aqua")   # 建立 ttk 的Label 物件
ttk_label.pack()
print(tk_label.keys())          # 呼叫 keys() 傳回 tk 的 Label 物件屬性串列
print(ttk_label.keys())         # 呼叫 keys() 傳回 ttk 的 Label 物件屬性串列
win.mainloop()

此例分別建立一個 tk 與 ttk 的 Label 元件, 並呼叫其 keys() 方法來檢視屬性串列, 結果如下 :




>>> %Run Label_1.py
['activebackground', 'activeforeground', 'anchor', 'background', 'bd', 'bg', 'bitmap', 'borderwidth', 'compound', 'cursor', 'disabledforeground', 'fg', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'justify', 'padx', 'pady', 'relief', 'state', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength']
['background', 'foreground', 'font', 'borderwidth', 'relief', 'anchor', 'justify', 'wraplength', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'image', 'compound', 'padding', 'state', 'cursor', 'style', 'class']

上方是 tk 的 Label 物件屬性, 下方則是 ttk 的 Label 物件屬性, 可見 ttk 可用的屬性參數比 tk 少很多, 例如它沒有 bg, padx, pady 等參數 (ttk 的背景色要用 background, 不能用 bg). 由於 Label 元件外觀在 tk 與 ttk 沒有明顯差別, 因此對於 Label 元件而言, 其實只要使用 tkinter 版的即可. 

其次, 標籤文字外圍預設沒有框邊 (因為 relief 參數預設為 "flat"), 如果要讓 Label 有框邊, 則在建立物件時要傳入 relief 參數, 如下面範例所示 : 


測試 2 : 建立標籤文字並設定參數 [看原始碼]

import tkinter as tk
win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("400x200")                                
label1=tk.Label(win, text="Hello World")
label1.pack()
label2=tk.Label(win, text="你是在說哈囉嗎?", underline=3)
label2.pack()
label3=tk.Label(win, text="洪車瑛(全汝斌)", padx=5, pady=5, relief="solid")
label3.pack()
label4=tk.Label(win, text="文森佐(宋仲基)", bg="aqua", fg="blue")
label4.pack()
label5=tk.Label(win, text="黑道律師文森佐", font="微軟正黑體 16 bold")
label5.config(bd=3, relief="solid")
label5.pack()
win.mainloop()

此例測試了一些 Label 元件的參數, 例如 relief (邊框樣式), underline (底線字元索引), bg (背景), fg (前景), padx (水平間距), pady (垂直間距), 以及 font (字型樣式) 等, 結果如下 :




注意 label2 元件將參數 underline 設為 3 (字元索引), 表示要將第 4 個字元 "說" 加上底線. 由上面的範例可知, Label 元件預設無邊框 (relief="flat"), 下面範例測試 6 種 relief 參數值 :


測試 3 : tk 的 relief 邊框參數測試 [看原始碼]

import tkinter as tk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("400x200")                                
label1=tk.Label(win, text="relief='flat'", relief="flat")                # 預設無邊框
label1.pack()
label2=tk.Label(win, text="relief='sunken'", relief="sunken")    # 標籤凹下
label2.pack()
label3=tk.Label(win, text="relief='groove'", relief="groove")    # 邊框凹下
label3.pack()
label4=tk.Label(win, text="relief='raised'", relief="raised")       # 標籤凸起
label4.pack()
label5=tk.Label(win, text="relief='ridge'", relief="ridge")          # 邊框凸起
label5.pack()
label6=tk.Label(win, text="relief='solid'", relief="solid")           # 實線邊框
label6.pack()

win.mainloop()

結果如下 : 




下面範例改用 ttk 來測試 6 種邊框效果 : 


測試 4 : ttk 的 relief 邊框參數測試 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("400x200")                                
label1=ttk.Label(win, text="relief='flat'", relief="flat")                # 預設無邊框
label1.pack()
label2=ttk.Label(win, text="relief='sunken'", relief="sunken")    # 標籤凹下
label2.pack()
label3=ttk.Label(win, text="relief='groove'", relief="groove")    # 邊框凹下
label3.pack()
label4=ttk.Label(win, text="relief='raised'", relief="raised")       # 標籤凸起
label4.pack()
label5=ttk.Label(win, text="relief='ridge'", relief="ridge")          # 邊框凸起
label5.pack()
label6=ttk.Label(win, text="relief='solid'", relief="solid")           # 實線邊框
label6.pack()

win.mainloop()

結果如下 : 




與上面的 tk 標籤比較, 主要差別是 ttk 的 solid 邊框較細顏色較淡, 而 tk 的邊框是較粗的黑線, 其餘的邊框效果看來差不多. 

邊框預設粗細為 1px, 可以用 borderwidth 加以設定, 注意, 在 tk 可以用 bd 或 borderwidth, 但在 ttk 只能用 borderwidth. 另外因為預設的垂直與水平間距為 1px, 這讓標籤文字與邊框太過於接近, 下面範例用 config() 方法加以設定. 注意, tk 用 padx 與 pady 設定間距 (整數); 而 ttk 則用 paading 參數, 是格式為 [left, top, right, bottom] 的串列 (元組也可以), 例如 : 
 

測試 5 : 設定邊框粗細與間距 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("400x250")
# tk
tk_label1=tk.Label(win, text="relief='flat'", relief="flat")
tk_label1.config(bd=3, padx=5, pady=5)          # tk 邊框用 bd 或 borderwidth 均可
tk_label1.grid(row=0, column=0)
tk_label2=tk.Label(win, text="relief='sunken'", relief="sunken")
tk_label2.config(bd=3, padx=5, pady=5)
tk_label2.grid(row=1, column=0)
tk_label3=tk.Label(win, text="relief='groove'", relief="groove")
tk_label3.config(bd=3, padx=5, pady=5)
tk_label3.grid(row=2, column=0)
tk_label4=tk.Label(win, text="relief='raised'", relief="raised")
tk_label4.config(bd=3, padx=5, pady=5)
tk_label4.grid(row=3, column=0)
tk_label5=tk.Label(win, text="relief='ridge'", relief="ridge")
tk_label5.config(bd=3, padx=5, pady=5)
tk_label5.grid(row=4, column=0)
tk_label6=tk.Label(win, text="relief='solid'", relief="solid")
tk_label6.config(bd=3, padx=5, pady=5)
tk_label6.grid(row=5, column=0)
# ttk
ttk_label1=ttk.Label(win, text="relief='flat'", relief="flat")
ttk_label1.config(borderwidth=3, padding=[5, 5, 5, 5])    # ttk 邊框只能用 borderwidth 
ttk_label1.grid(row=0, column=1)
ttk_label2=ttk.Label(win, text="relief='sunken'", relief="sunken")
ttk_label2.config(borderwidth=3, padding=[5, 5, 5, 5])
ttk_label2.grid(row=1, column=1)
ttk_label3=ttk.Label(win, text="relief='groove'", relief="groove")
ttk_label3.config(borderwidth=3, padding=[5, 5, 5, 5])
ttk_label3.grid(row=2, column=1)
ttk_label4=ttk.Label(win, text="relief='raised'", relief="raised")
ttk_label4.config(borderwidth=3, padding=[5, 5, 5, 5])
ttk_label4.grid(row=3, column=1)
ttk_label5=ttk.Label(win, text="relief='ridge'", relief="ridge")
ttk_label5.config(borderwidth=3, padding=[5, 5, 5, 5])
ttk_label5.grid(row=4, column=1)
ttk_label6=ttk.Label(win, text="relief='solid'", relief="solid")
ttk_label6.config(borderwidth=3, padding=(5, 5, 5, 5))                 # padding 用元組亦可
ttk_label6.grid(row=5, column=1)

win.mainloop()

此例改用 grid() 排版以利比較 tk 與 ttk 的差異, 結果如下 : 




奇怪的是, ttk 的 borderwidth 參數似乎無效, 不管設定為多少都沒變, why? 不過 ttk 的 padding 有個好處是設定較靈活, 上下左右都可以不同值, tk 的 padx, pady 使左右間距一樣, 上下間距一樣. 

從以上範例可知, 標籤文字預設是置中 ("center") 對齊, 可用 justify 參數設定向左 ("left") 或向右 ("right") 對齊. 下面範例同時也測試 wraplength 參數, 它可指定標籤文字每幾個 px 強制跳行 : 


測試 6 : 用 justify 參數設定對齊方式 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("400x200")
msg="人生就像騎腳踏車,想保持平衡就得往前走。"
label1=ttk.Label(win, text=msg, wraplength=200)
label1.config(relief="ridge")
label1.pack()
label2=ttk.Label(win, text=msg, wraplength=300, justify="left")
label2.config(relief="ridge")
label2.pack()
label3=ttk.Label(win, text=msg, wraplength=300, justify="right")
label3.config(relief="ridge")
label3.pack()

win.mainloop()

此例利用 wraplength 參數強制每 300px 換行顯示以測試 justify 參數之左中右對齊效果, 同時也設定邊框以利觀察, 可見 label1 預設置中對齊, label2 為向左對齊, 而 label 為向右對齊, 結果如下 :  




Label 元件除了可以利用 text 參數放置標籤文字外, 還可以利用 image 參數放置圖片標籤, 此參數需要一個 PhotoImage 物件, tkinter 提供了 PhotoImage() 函數可將 gif 或 png 等圖檔轉成 PhotoImage 物件 (目前尚不支援 jpeg 檔). 

下面範例使用的圖檔取自維基百科中的 Lenna 圖, 原圖為 300x300 的 jpeg 檔, 下載後用小畫家更改解析度為 200x200 並存成 png 檔 :


測試 7 : 用 image 參數設定圖片標籤 [看原始碼] [200x200 png Lenna 圖]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("400x300")
lenna=tk.PhotoImage(file="200x200-Lenna.png")   # 從指定知圖檔建立 PhotoImage 物件
print(type(lenna))                                                        # 輸出 <class 'tkinter.PhotoImage'>
label1=ttk.Label(win, image=lenna)                          # 用 image 參數建立圖片標籤
label1.config(relief="solid", padding=10)
label1.pack()

win.mainloop()

此例呼叫 tk 的 PhotoImage() 函數並傳入 file 參數指定圖檔路徑與名稱來建立一個 PhotoImage 物件, 然後將此 PhotoImage 物件傳入 Label() 函數的 image 參數即可. 注意, PhotoImage() 是放在 tk 底下的函數, ttk 並未重複支援, 不能呼叫 ttk.PhotoImage(). 結果如下 : 




Label 元件的標籤內容也可以同時使用圖片與文字, 這需要用 compound 參數來指定文字與圖片的相對關係 (compound="left"/"center"/"right"/"top"/"bottom", 分別表示圖片靠左, 中, 與右). 例如 : 


測試 8 : 用 image 與 compond 參數 (left/center/right) 設定圖文標籤 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("500x700")
lenna=tk.PhotoImage(file="200x200-Lenna.png")
print(type(lenna))
msg="""影像處理教科書上著名的 Lenna 圖,取自於 1972 年 Playboy 雜誌上
模特兒 Lenna Söderberg 的照片。"""
label1=ttk.Label(win, text=msg, justify="left", image=lenna, compound="right")
label1.config(relief="solid", padding=10, wraplength=200)
label1.pack()
label2=ttk.Label(win, text=msg, justify="left", image=lenna, compound="left")
label2.config(relief="solid", padding=10, wraplength=200)
label2.pack()
label3=ttk.Label(win, text=msg, justify="left", image=lenna, compound="center")
label3.config(relief="solid", padding=10, wraplength=200)
label3.pack()
win.mainloop()

此例在視窗中建立了三個圖文標籤, 利用 compound 參數分別設定圖片靠右, 左, 以及在中央, 文字則利用 wraplength 強制 200px 換行顯示, 結果如下 : 




參數 compound 也可以指定圖片靠上 (="top") 或靠下 (="bottom"), 例如 : 


測試 9 : 用 image 與 compond 參數 (top/bottom) 設定圖文標籤 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("500x400")
lenna=tk.PhotoImage(file="200x200-Lenna.png")
print(type(lenna))
msg="""影像處理教科書上著名的 Lenna 圖,取自於 1972 年 Playboy 雜誌上
模特兒 Lenna Söderberg 的照片。"""
label1=ttk.Label(win, text=msg, justify="left", image=lenna, compound="top")
label1.config(relief="solid", padding=10, wraplength=200)
label1.grid(row=0, column=0)
label2=ttk.Label(win, text=msg, justify="left", image=lenna, compound="bottom")
label2.config(relief="solid", padding=10, wraplength=200)
label2.grid(row=0, column=1)
win.mainloop()

結果如下 :




但是如果同時指定了 text 與 image 參數, 卻沒指定 compound 參數, 則 text 會被忽略, 僅顯示圖片, 換句話說, text 相當於無效, 如下範例所示 :


測試 10 : 指定 text 與 image 卻沒指定 compond 時 text 會被忽略 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("500x700")
lenna=tk.PhotoImage(file="200x200-Lenna.png")
print(type(lenna))
msg="""影像處理教科書上著名的 Lenna 圖,取自於 1972 年 Playboy 雜誌上
模特兒 Lenna Söderberg 的照片。"""
label1=ttk.Label(win, text=msg, justify="left", image=lenna)
label1.config(relief="solid", padding=10, wraplength=200)
label1.pack()
win.mainloop()

此例同時指定了 text 與 image 參數, 卻沒有指定 compound 參數, 結果只顯示圖片 : 




除了可在標籤中放置自訂的圖片外, Tkinter 也內建了 10 個圖示可透過 bitmap 參數放置在 Label 上, 但此功能僅 tk 有, ttk 並沒有 bitmap 參數可用, 例如 : 


測試 11 : 使用內建的位元圖 bitmap 參數 (ttk 無此參數) [看原始碼]

import tkinter as tk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("300x250")
label1=tk.Label(win, text="訊息", bitmap="info")     # 使用 bitmap 時 text 無效
label1.config(relief="groove", width=200)
label1.pack()
label2=tk.Label(win, bitmap="warning")
label2.config(relief="groove", width=200)
label2.pack()
label3=tk.Label(win, bitmap="error")
label3.config(relief="groove", width=200, height=30)
label3.pack()
label4=tk.Label(win, bitmap="question")
label4.config(relief="groove", width=200)
label4.pack()
label5=tk.Label(win, bitmap="questhead")
label5.config(relief="groove", padx=10, pady=10)      # padx 與 pady 無效
label5.pack()
label6=tk.Label(win, bitmap="gray12")
label6.config(relief="groove", width=200)
label6.pack()
label7=tk.Label(win, bitmap="gray25")
label7.config(relief="groove", width=200)
label7.pack()
label8=tk.Label(win, bitmap="gray25")
label8.config(relief="groove", width=200)
label8.pack()
label9=tk.Label(win, bitmap="gray50")
label9.config(relief="groove", width=200)
label9.pack()
label10=tk.Label(win, bitmap="gray75")
label10.config(relief="groove", width=200)
label10.pack()
win.mainloop()

結果如下 : 




可見 text 與 bimap 參數同時使用時 text 參數無效 (如 label1 所示); 同時 padx 與 pady 對 bitmap 標籤也是無效 (如 label5 所示). 

還有一個元件共通參數 anchor, 當元件的尺寸大於內容 (文字或圖片) 時, 此參數用來定位內容在在元件中的 9 個方位, 即 "n" (北), "e" (東), "s" (南), "w" (西), "nw" (西北), "ne" (東北), "sw" (西南), "se" (東南), "center" (中央) : 




注意, anchor 參數的值除了上述的方位字串外, 也可以用 tk 的常數, 例如 "sw" 可用 tk.SW, 例如 :


測試 12 : 使用內建的位元圖 bitmap 參數 (ttk 無此參數) [看原始碼]

import tkinter as tk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("600x600")
label1=tk.Label(win, text="Hello", anchor="nw")
label1.config(relief="groove", width=15, height=7)
label1.grid(row=0, column=0)
label2=tk.Label(win, text="Hello", anchor="n")
label2.config(relief="groove", width=15, height=7)
label2.grid(row=0, column=1)
label3=tk.Label(win, text="Hello", anchor="ne")
label3.config(relief="groove", width=15, height=7)
label3.grid(row=0, column=2)
label4=tk.Label(win, text="Hello", anchor="w")
label4.config(relief="groove", width=15, height=7)
label4.grid(row=1, column=0)
label5=tk.Label(win, text="Hello", anchor="center")
label5.config(relief="groove", width=15, height=7)
label5.grid(row=1, column=1)
label6=tk.Label(win, text="Hello", anchor="e")
label6.config(relief="groove", width=15, height=7)
label6.grid(row=1, column=2)
label7=tk.Label(win, text="Hello", anchor="sw")
label7.config(relief="groove", width=15, height=7)
label7.grid(row=2, column=0)
label8=tk.Label(win, text="Hello", anchor=tk.S)
label8.config(relief="groove", width=15, height=7)
label8.grid(row=2, column=1)
label9=tk.Label(win, text="Hello", anchor=tk.SE)
label9.config(relief="groove", width=15, height=7)
label9.grid(row=2, column=2)
win.mainloop()

此例使用 grid 排版了 3x3 的九宮格, 每格放一個大小較文字內容 "Hello" 還大的 Label 元件, 並以 anchor 參數指定了相對應的方位, 結果如下 : 




可見文字內容顯示在 anchor 所指定的方位上. 

參考 :


2021年7月19日 星期一

柚子媽媽上菜的咕咾肉

上週的一葉日式便當主菜之一是咕咾肉, 酸酸甜甜的滋味甚好, 就上網研究





材料 :
  • 豬後腿肉 一塊 (400 g)
  • 鳳梨 1/2 或 1/4 片 (100 g)
  • 紅椒 一顆
  • 洋蔥一顆
  • 蛋一顆
  • 醬油
  • 米酒
  • 白胡椒粉
  • 香油 
作法 : 
  1. 紅椒, 洋蔥, 鳳梨切小塊備用.
  2. 豬後腿肉先切片, 用刀背將肉片兩面拍鬆後切成 2 公分的小肉丁.
  3. 取鳳梨 2~3 小塊切成細末, 用天然的鳳梨酵素來醃肉可讓肉質軟嫩. 
  4. 將鳳梨細末與小肉丁放入碗中, 倒入一茶湯匙米酒與醬油, 加少許白胡椒粉, 一大匙蛋液, 用手將肉丁與醃料抓勻, 讓醃料被肉丁充分吸收後再加入一茶匙太白粉抓勻, 然後醃十分鐘即可 (太久肉會太軟). 
  5. 取另一碗製作醬汁, 倒入細砂糖, 白醋, 番茄醬各 2 大匙, 水 1 大匙後攪拌均勻. 
  6. 取另一碗倒入適量太白粉, 把醃好的肉丁放進去均勻裹上太白粉, 然後每一塊握在手裡壓實放入盤中備用.
  7. 熱鍋後倒入 600 cc 油, 待油溫升到 150 度後 (撒點太白粉進去會起泡泡) 轉小火, 將肉丁一一放入鍋中油炸, 先不要攪動, 等肉丁定型約一分鐘後再翻動肉丁使其受熱均勻, 約炸 4~5 分鐘肉丁變金黃色後即可撈起控油. 
  8. 熱鍋倒入兩茶匙油, 放入洋蔥, 紅椒塊大火拌炒 30 秒, 放入鳳梨再拌炒 20 秒, 倒入調好的醬汁, 轉小火將醬汁煮滾, 然後轉大火一邊倒入太白粉水, 一邊攪拌直到變成濃稠狀, 然後倒入炸好的肉丁, 大火拌炒 15 秒, 淋上 1 茶匙香油大火拌炒 10 秒收汁即可起鍋了. 
嗯, 好像不難做, 這個周末就來試做看看. 

下面這個影片也值得參考 : 


原來肉先加鹽抓勻可讓肉質變嫩, 而且蛋液只用蛋黃, 不用蛋白. 油炸時要用覆炸方式 (即撈起等油溫再次上升時下鍋再炸一次) 肉丁才會外脆內軟, 這在大廚羅子昭的影片中也有提到 :


2021年7月18日 星期日

2021 年第 29 周記事

本周專心學習 Numpy, 發現平常走馬看花, 翻書翻得很快, 但靜下心來仔細測試才發覺眉角還真不少, 好的書重點都會點出來 (我手上幾本日本人寫的書通常都不馬虎), 爛的書就隨便舉個例子帶過, 所以自修學問買本好書真的很重要. 

鄉下的芒果上週已全部採完, 今年全靠路旁那三棵樹, 產量不到 100 顆, 真是慘淡! 現在是寄望七月底的龍眼, 我今天採了幾顆來吃, 甜度與果肉約 8 成, 大概下周可以開始採, 小舅今天拿粽子來採了兩把回去嚐鮮.

這幾天水某跟同事合購了 NetFlix 帳號, 回來在安博盒子安裝 App, 首看節目是最近很熱的韓劇 "黑道律師文森佐", 由宋仲基與全汝彬擔綱演出, 連續看了三天, 越看越有趣, 以前追韓劇的 Fu 又回來了. 期盼已久的 Voice 4 在愛奇藝上映已到第 8 集, 雖然也不錯看, 但感覺與之前的 Voice 比起來張力沒這麼強了. 

菁菁上週去肯德基面試打工, 她說全班只有她還沒打過工, 我想讓她去體驗一下工作的感覺, 賺自己的零用錢也好. 

上週二哥要我將鄉下那台電腦帶回來, 因為高雄這台桌電硬碟快要 GG 了 (有時開不起來), 他買了一顆 SSD 來重灌, 哇, 兩天就搞定, 現在要我裝 PC 已經太為難我了. 他主要是趁暑假玩 Game, 有影像卡剛好可以來試試 CUDA 函式庫.  

今天傍晚煮完絲瓜正準備要煮紅燒板豆腐, 卻發現水龍頭沒水了, 跟爸去張家伙房門口查自來水水表, 發現指針沒轉, 不知是哪裡有問題, 原先懷疑是最近阿正兄為芭樂鑽孔打樁弄斷水管, 但這樣水管應該是漏水, 指針狂轉才對, 要等明天請水公司查. 水塔水位與自來水流量要列入居家物廉網監控一環才行. 

2021年7月17日 星期六

樹莓派架站 (十一) : 設定固定 IP 的方法

上週將 Pi 4 交給湘雲老師帶回學校, 用網路線連接到研究室網路 RJ45 插槽後開機, 我在遠端用 VNC Cloud 卻怎麼也無法與 Pi 4 連線. 經與圖資的劉老師請教, 原來學校的網路環境是有特別規劃的, 聯網主機必須指定 IP, gatway, mask, 以及 DNS server 等網址設定才可以連到學校網路, 我對網路底層一知半解, 以為像家裡一樣直接接上網路線就能讓 Pi 4 上 Internet, 實在太天真了. 

我之前在學習樹莓派時也做過固定 IP 的設定測試, 作法有兩種,例如下面這篇是透過 /etc/network/interface 這個檔案設定固定的無線網路 IP, 但在新版 Raspbian 已不管用了 :


下面這篇是新的做法, 是透過 /etc/dhcpcd.conf 這個檔來設定無線網路之固定 IP : 


所以之前都是在設定無線網路, 那固定網路該怎麼做呢? 我查詢了一些網路上的作法, 發現只要將 wlan0 改成 eth0 應該就可以, 參考 : 





綜合上面的資料, 可知設定固網 IP 基本上有兩種做法 : 


1. 使用 /etc/network/interfaces.d/eth0 檔 :

也可以直接使用 /etc/network/interface, 但這個檔案的預設內容為 :

source-directory /etc/network/interfaces.d

亦即此網路介面檔預設會從 /etc/network/interfaces.d 這目錄匯入底下的設定檔, 所以就沒有必要直接將網路設定資訊寫在這裡, 而是寫在此目錄下的檔案中, 例如固網就用 eth0 這檔名, 用 nano 編輯如下內容, 然後存成 eth0 檔案 :

auto eth0
iface eth0 inet static
        address 192.168.2.193
        netmask 255.255.255.0
        gateway 192.168.2.1
        dns-nameservers 168.95.1.1 8.8.8.8

此處 address 就是我要的固定 IP=192.168.2.193, 網路遮罩 netmask=255.255.255.0 表示最後一個 byte 為主機位址, 預設閘道 gateway=192.168.2.1 為我家基地台的 IP, 而 dns-server 所列兩個網址是 Google 的 DNS 伺服器網址. 網路遮罩可以在樹莓派聯網情況下用 ifconfig 查得; 而預設閘道則可用 route -ne 指令查得, 參考 :


pi@raspberrypi:~ $ route -ne
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         192.168.2.1     0.0.0.0         UG        0 0          0 eth0
192.168.2.0     0.0.0.0         255.255.255.0   U         0 0          0 eth0




也可以用 Windows 的 ipconfig 指令查詢 : 




將上面的 /etc/network/interfaces.d/eth0 存檔後檢視內容如下 :

pi@raspberrypi:~ $ cat /etc/network/interfaces.d/eth0    
auto eth0
iface eth0 inet static
        address 192.168.2.193
        netmask 255.255.255.0
        gateway 192.168.2.1
        dns-nameservers 168.95.1.1 8.8.8.8

然後下 sudo reboot 重開機, 果然順利取得固定 IP :




2. 使用 /etc/dhcpcd.conf 檔 :

為了確定這種方式可行, 不受上面第一種方法干擾, 所以先將上面的 /etc/network/interfaces.d/eth0 這個檔備份到 pi 目錄下保存然後刪除 : 

pi@raspberrypi:~ $ sudo mv /etc/network/interfaces.d/eth0 eth0     
pi@raspberrypi:~ $ cat /etc/network/interfaces.d/eth0    
cat: /etc/network/interfaces.d/eth0: 沒有此一檔案或目錄

確定檔案已刪除後, 將系統預設的 /etc/dhcpcd.conf 複製到根目錄下保存 : 

pi@raspberrypi:~ $ sudo cp /etc/dhcpcd.conf Downloads/dhcpcd.conf

然後編輯一個 dhcpcd.conf 檔, 內容如下 : 

interface eth0 
static ip_address=192.168.2.193/24 
static routers=192.168.2.1 
static domain_name_servers=168.95.1.1 8.8.8.8

pi@raspberrypi:~ $ nano dhcpcd.conf    

然後將其複製到 /etc 下覆蓋同名檔案 : 

pi@raspberrypi:~ $ sudo mv dhcpcd.conf /etc/dhcpcd.conf     
pi@raspberrypi:~ $ cat /etc/dhcpcd.conf    
interface eth0 
static ip_address=192.168.2.193/255.255.255.0 
static routers=192.168.2.1 
static domain_name_servers=168.95.1.1 8.8.8.8

確定內容無誤後重開機 : 

pi@raspberrypi:~ $ sudo reboot    

結果亦可成功取得指定之固定 IP 並連上 Internet : 

pi@raspberrypi:~ $ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.2.193  netmask 255.255.255.0  broadcast 192.168.2.255
        inet6 fe00::ba27:ebff:fe60:2e70  prefixlen 64  scopeid 0x20<link>
        ether b8:07:e0:63:20:7b  txqueuelen 1000  (Ethernet)
        RX packets 678  bytes 88315 (86.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 595  bytes 288638 (281.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


2021-07-18 補充 :

檢查鄉下的 Pi 3 網路設定, 採用的是第二種方法 (/etc/dhcpcd.conf), 內容如下 :

pi@raspberrypi:~ $ cat /etc/dhcpcd.conf     
interface wlan0 
static ip_address=192.168.2.192 
static routers=192.168.2.2 
static domain_name_servers=192.168.2.2

而無線網路上網設定檔則是放在 /etc/wpa_supplicant/wpa_supplicant.conf 中, 內容為 : 

pi@raspberrypi:~ $ cat /etc/wpa_supplicant/wpa_supplicant.conf    
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=TW

network={
ssid="TonyNote8"
psk="a123456"
key_mgmt=WPA-PSK
}

network={
ssid="EDIMAX-tony"
psk="a123456"
key_mgmt=WPA-PSK
}

network={
ssid="EDIMAX-meinung"
psk="a123456"
key_mgmt=WPA-PSK
}
 
總結來說, 新的 Raspbian 映像檔燒錄好後先不要拔除讀卡機, 先準備一個空白的 ssh 檔案與含有如下內容的 wpa_supplicant.conf 檔 (ssid 與 psk 要改成自己的基地台帳密), 複製到燒錄好映象檔的 boot 碟下 : 

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev 
update_config=1
country=TW

network={
    ssid="TonyNote8"
    psk="a123456"
    }

注意, 這個放在 root 碟下的 wpa_supplicant.conf 在開機後會被複製到 /etc/wpa_supplicant/wpa_supplicant.conf 這個檔. 

這樣在開機後樹莓派就會自動連上指定的基地台, 因為 Raspbian 預設已開啟 ssh 功能, 因此就可以用 Putty 連線樹莓派 (預設帳號 pi, 密碼 raspberry) 進行無頭存取, 即樹莓派開機前不需接螢幕與鍵鼠組, 直接用 PC 遠端存取, 用 ifconfig 取得樹莓派區網 IP 後, 就可以用 VNC viewer (local) 從 PC 連線樹莓派進行遠端桌面存取. 進入遠端桌面後首先開啟瀏覽器登入 VNC cloud 帳號, 並為這台樹莓派取一個容易辨識的名稱, 這樣以後就可以用 VNC viewer (cloud) 從外網遠端存取樹莓派了. 

如果要讓樹莓派取得固定 IP, 就可以用本篇的兩個方法設定, 例如用 dhcpcd.conf 設定的話, 可以編輯 /etc/dhcpcd.conf 加入如下內容 :

interface eth0 
static ip_address=192.168.2.193/255.255.255.0 
static routers=192.168.2.2 
static domain_name_servers=168.95.1.1 8.8.8.8

interface wlan0 
static ip_address=192.168.2.193/255.255.255.0 
static routers=192.168.2.2 
static domain_name_servers=168.95.1.1 8.8.8.8

這樣樹莓派不論是用有線還是無線方式上網都能獲得指定之固定 IP 了, 方便區網存取或做伺服器時用 (避免系統重啟後 IP 跑掉). 

以上補充說明總結樹莓派從燒錄映像檔到無頭存取的網路設定, 以及透過 VNC Cloud 進行外網遠端存取的過程, 作為以後裝機的參考. 

2021年7月14日 星期三

Python 內建 GUI 模組 tkinter 測試 (四) : 基本用法複習整理

我在 2016 年中開始學習 Python 內建的 GUI 模組 tkinter, 才整理了一篇入門筆記就停擺了. 到了2018 年想改寫語音處理程式再次拾起, 但也只再寫了兩篇筆記又停下來. 最近為了工作上方便管理資料庫, 計畫要寫個 GUI 介面搭配網路爬蟲來使工作自動化, tkinter 的學習經過了五年還停滯不前實在說不過去, 所以想趁此機會把 tkinter 一口氣學完, 故先複習之前的筆記並做個摘要整理. 

本系列之前的文章參考 :

Python 內建 GUI 模組 tkinter 測試 (一) : 建立視窗
Python 內建 GUI 模組 tkinter 測試 (二) : 對話框
Python 內建 GUI 模組 tkinter 測試 (三) : 版面管理員

tkinter 的說明文件參考 :

https://pythonspot.com/en/tkinter/
Graphical User Interfaces with Tk

茲將前三篇筆記摘要整理如下 : 


1. 匯入模組 :

這幾年因為機器學習的推波助瀾, Python 3 現在已是主流, 所以學習 GUI 套件應該改用 Python 3 的 tkinter 模組 (注意是小寫的 t), 而非 Python 2 的 Tkinter 模組 (注意是大寫的 T). 使用 tkinter 模組通常即可開發一般的 GUI 程式, 但若要使用更進階的元件也可以同時匯入其子模組 ttk : 

>>> import tkinter as tk                     # 匯入 tkinter 並取簡名 tk
>>> from tkinter import ttk               # 匯入 tkinter 的子模組 ttk
>>> tk  
<module 'tkinter' from 'C:\\Python37\\lib\\tkinter\\__init__.py'>
>>> ttk   
<module 'tkinter.ttk' from 'C:\\Python37\\lib\\tkinter\\ttk.py'>

原始版的 tkinter 提供了 19 種視窗元件 (widget, 或稱控制項), 其外觀依賴於作業系統, 為了美化視窗元件外觀與提供更多好用元件, tkinter 另外發展了主題版 (Themed tkinter) 的子模組 ttk, 它提供了 18 種外觀美化的主題元件 (其中 12 種與 tkinter 相同), 合計有 25 種元件可用 :  


 tkinter 元件 ttk 元件 說明
 1. Label 1. Label 標籤文字
 2. Button 2. Button 按鈕
 3. Radiobutton 3. Radiobutton 單選圓鈕
 4. Checkbutton 4. Checkbutton 核取方塊 (多選)
 5. Entry 5. Entry 文字欄位 (單行)
 6. Text  文字區域 (多行)
 7. Scrollbar 6. Scrollbar 捲軸
 8. Listbox  清單方塊
 9. Spinbox 7. Spinbox 數值微調器
 10. Scale 8. Scale 尺規
 11. Frame 9. Frame 框架
 12. LabelFrame 10. LabelFrame 標籤框架
 13. Toplevel  頂層視窗
 14. PanedWindow 11.PanedWindow 視窗面板
 15. Menu  選單
 16. OptionMenu  功能表
 17. Menubutton 12. MenuButton 選單按鈕
 18. Canvas  畫布
 19. Message  訊息 (可多行)
  13. Combobox 下拉式選單
  14. Notebook 頁籤面板
  15. Progressbar 進度條
  16. Separator 分隔線
  17. Sizegrip 尺寸調整器
  18. Treeview 樹狀表格


2. 視窗程式基本架構 :    

tkinter 模組基本視窗程式結構如下 : 

import tkinter as tk                                  # 匯入 tkinter 模組並取簡名為 tk
root=tk.Tk()                                             # 建立根視窗 Tk 物件 
root.title("Hello")                                     # 設定標題
label=tk.Lable(root, "Hello!")                 # 建立標籤元件
label.pack()                                              # 將標籤元件加入視窗
root.mainloop()                                        # 將根視窗加入事件監視迴圈並持續描繪視窗

使用 ttk 的基本架構如下 :

import tkinter as tk                                   # 匯入 tkinter 模組並取簡名為 tk
from tkinter import ttk                             # 匯入 ttk 子模組
root=tk.Tk()                                             # 建立根視窗 Tk 物件 (仍用 tk)
root.title("Hello")                                     # 設定標題
label=ttk.Lable(root, "Hello!")               # 建立標籤元件
label.pack()                                              # 將標籤元件加入視窗
root.mainloop()                                        # 將根視窗加入事件監視迴圈並持續描繪視窗

注意, 建立根視窗物件仍然要用 tk.Tk(), 建立元件才用 ttk.

如果視窗元件物件後續不會再用到就不須建立物件參考, 可以直接用鏈式呼叫 :

ttk.Label(root, "Hello").pack()   

視窗預設是可拖曳調整大小, 放到最大或縮小, 也可以呼叫根視窗 Tk 物件的 geometry() 方法設定視窗大小 :

root.geometry("400x300")

呼叫根視窗物件的 resizable() 方法傳入 (0,0) 或 (False, False) 可限制視窗不可調整大小 : 

root.resizable(0,0)                         # 設定視窗不可變更大小
root.resizable(False, False)           # 設定視窗不可變更大小


3. 建立元件 : 

原始版 tkinter 使用模組簡名 tk 呼叫方法, 主題版則用子模組名稱 ttk. 


(1). 標籤元件 : 

label=tk.Label(root, text="Hello World!")

更改標籤內容 :

label.configure("你是在說哈囉嗎?")


(2). 按鈕元件 : 

button=tk.Button(root, text="OK", command=clickOK)    

按鈕可用 command 參數綁定一個事件處理函式名稱 (不用括號), 此處為 clickOK, 須在呼叫之前定義. 另外一個方法是呼叫元件的 bind() 方法來綁定指定的事件, 例如按滑鼠右鍵或按下鍵盤等, bind() 的介面如下 :

bind(event, handler)   

第一參數 event 是一個代表特定事件的字串, 例如最常用的是 "<Button-1>" 表示按下滑鼠左鍵事件, 而 "<Double-Button-1>" 則表示連續按滑鼠左鍵兩次  (滑鼠左, 中, 右鍵分別以數字 1, 2, 3 表示), 例如 :

button1.bind("<Button-1>", clickOK)

第二參數 handler 為事件處理函式名稱 (不用括號), 但與使用 command 參數綁定不同的是, 定義此 handler 函式時須傳入事件變數 (否則會出現錯誤), 例如 :

def clickOK(e):
    pass

利用此事件物件可取得此事件之相關資訊, 例如 e.x 與 e.y 可取得滑鼠左鍵之座標. 

關於更多 Tkinter 事件處理細節可參考 "Introduction to Programming Using Python (Python 程式設計入門指南)" 這本書的 9-11 節. 


(3). 元件排版 : 

tkinter 元件提供 pack(流水式), grid(網格式), 以及 place(絕對與相對位置) 三種排版方法, 其中 pack 與 grid 兩種方式互斥, 一個視窗容器中不可同時使用 pack 與 grid, 但 place 排版中則可以同時用 pack 與 grid. 

流水式排版 : 呼叫元件的 pack() 方法可將元件如流水般順序放入視窗容器, 預設是由下而上堆疊, 但可以利用可選參數 side (tk.TOP/tk.BOTTOM/tk.LEFT, tk.RIGHT/tk.CENTER) 與 anchor (tk.E/tk.W/tk.S/tk.N/tk.NW/tk.SW/tk.NE/tk.SE/tk.CENTER) 指定元件的放置方位與錨定位置, 預設是 tk.TOP 與 tk.CENTER, 故元件預設會由上而下置中放置 :   

label.pack()
button.pack()
button.pack(side=tk.LEFT, anchor=tk.S)

網格式排版 : 呼叫元件的 grid() 方法 

label.grid(row=0, column=0)     
button.grid()    # 多列單行
button.grid(row=0, column=1)


4. 對話框 : 

tkinter 透過三個子模組提供完整的對話框功能 :


 tkinter 對話框類別 說明
 messagebox 提供訊息輸出與確定, 取消, 是, 否等按鈕回應功能 (輸出)
 simpledialog 提供輸入框與確定, 取消等按鈕回應功能 (輸入)
 filedialog 提供檔案開啟與儲存對話框功能


其中訊息盒 messagebox 透過方法呼叫提供 8 種訊息盒類型, 語法如下 : 

messagebox.functionName(title, message [, detail])

參數 message 是主訊息, 而可選的 detail 則是說明訊息. 


 messagebox 方法 說明 按鈕 圖示 回傳值
 showinfo(title, massage [, detail]) 通知對話框 確定 i "ok"
 showwarning(title, massage [, detail]) 警告對話框 確定 ! "ok"
 showerror(title, massage [, detail]) 錯誤對話框 確定 x "ok"
 askyesno(title, massage [, detail]) 詢問對話框 是/否 ? True/False
 askokcancel(title, massage [, detail]) 詢問對話框 是/取消 ?  True/None
 askyesnocancel(title, massage[, detail]) 詢問對話框 是/否/取消 ? True/False/None
 askquestion(title, massage[, detail]) 詢問對話框 是/否 ? "yes"/"no"
 askretrycancel(title, massage[, detail]) 詢問對話框 重試/取消 ? True/False


使用 messagebox 要先從 tkinter 匯入此子模組並取個方便的簡名例如 mb :

from tkinter import messagebox as mb

例如 :

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox

def hello():
    msgbox.showinfo("Info", "Hello World!")

root=tk.Tk()
root.title("Hello")
root.geometry("300x200")
button=ttk.Button(root, text="Hello", command=hello)
button.pack()
root.mainloop()

子模組 simpledialog 就是輸入盒的功能, 可讓使用者輸入資料供程式後續運算, 使用前須從 tkinter 匯入此子模組並取個簡名例如 sd, 語法如下 :

from tkinter import simpledialog as sd

simpledialog 提供了三個方法分別用來讀取字串, 整數, 以及浮點數三種型態之輸入 :


 simpledialog 的方法 說明 回傳值
 askstring(title, message) 顯示字串輸入對話框 字串
 askinteger(title, message) 顯示整數輸入對話框 整數
 askfloat(title, message) 顯示浮點數輸入對話框 浮點數
 

這三個方法有 title 與 message 這兩個必要參數, 其實對於數值輸入的 askinteger() 與 askfloat() 還有如下三個可選參數 :
  • minvalue : 設定最小值
  • maxvalue : 設定最大值
  • initialvalue : 設定初始值 (預設值) 
若輸入值超出 minvalue~maxvalue 的範圍會出現提示對話框.


5. tkinter 元件的共同屬性與方法 : 

每一個 tkinter 元件都是物件, 它們有各自專有之屬性與方法, 也有如下之共同屬性與方法: 


 tkinter 元件共同屬性 說明
 width 元件寬度, px 或字元數 (Label)
 height 元件高度, px 或字元數 (Label)
 bg/background 背景色, 可用標準顏色名稱字串例如 'blue' 或 "#0000ff"
 fg/foreground 前景色, 可用標準顏色名稱字串例如 'blue' 或 "#0000ff"
 anchor 元件的錨點位置, 可用 "n", "e", "s", "w", "nw", "ne","sw","se","center"
 font 字型, 例如 "Helvetic 20 bold italic" 或 ("Helvetic", 20, "bold", "italic")
 bitmaps 內建的圖形, 例如 "error","info", "question", "warning", "gray12" 等
 relief 元件的邊框, 例如 "groov", "flat", "raised", "ridge", "solid", "sunken"
 cursors 滑鼠在元件上時的游標型式, 例如 : "heart", "cross", "star" 等

 tkinter 元件共同方法 說明
 config() 即時更改元件屬性值
 keys() 以串列傳回元件的所有屬性值
 pack() 將元件以流水方式放入父容器中
 grid() 將元件以網格方式放入父容器中
 place() 將元件以絕對或相對定位方式放入父容器中


6. tkinter 的物件導向寫法 : 

為了程式碼可重用性 (例如協同開發大型程式時), tkinter 程式也可以採用物件導向寫法, 上例之物件導向寫法為 :

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox

class Hello():
    def __init__(self):
        root=tk.Tk()
        root.title("Hello")
        root.geometry("300x200")
        button=ttk.Button(root, text="Hello", command=self.hello)
        button.pack()
        root.mainloop()
    def hello(self):
        msgbox.showinfo("Info", "Hello World!")       
Hello()

或者標準一些的寫法 : 

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox

class Hello(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("tkinter GUI 測試")                           
        self.geometry("200x100")
        button=ttk.Button(self, text="Hello", command=self.hello)
        button.pack()
    def hello(self):
        msgbox.showinfo("Info", "Hello World!")       

if __name__ == "__main__":
    win=Hello()
    win.mainloop()

物件導向寫法要注意類別內存取屬性與方法時要用 self, 否則會出現函式未定義之 NameError. 


7. 變數類別 : 

Tkinter 的元件是一種 Python 物件, 其內容存取並非直接使用指定敘述, 而是透過 tkinter 所提供的四種變數類別 (對應 GUI 使用者介面常用的四種資料型別) : 


 tkinter 變數類別 說明
 IntVar 整數變數, 預設值 0
 DoubleVar 浮點數變數, 預設值 0.0
 BooleanVar 布林值變數, 預設 0 (False)
 StringVar 字串變數, 預設空字串 ""


呼叫這些類別的建構子即可建立變數物件, 例如 :

x=IntVar()
x=DoubleVar()
x=BooleanVar()
x=StringVar()

然後將這些變數指定給 tkinter 元件的 textvariable 或 variable 參數, 即可將此變數綁定到元件上, 只要呼叫變數物件的 get() 即可取得元件之內容 (例如取得 Entry 內使用者輸入之值), 呼叫變數物件的 set() 則可設定元件之內容 (例如設定 Entry 內容的預設值), 例如 : 

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")                           
win.geometry("200x150")

i=tk.IntVar()
d=tk.DoubleVar()
b=tk.BooleanVar()
s=tk.StringVar()
tk.Label(win, textvariable=i, bg="ivory", width=5).pack()
tk.Label(win, textvariable=d, bg="cyan", width=5).pack()
tk.Label(win, textvariable=b, bg="pink", width=5).pack()
tk.Label(win, textvariable=s, bg="gold", width=5).pack()
win.mainloop()

當這些變數被更改或元件內容被更改時, 對方的值也會動態地被更改, 因為變數物件與 tkinter 元件已經透過 textvariable 參數互相綁定在一起了. 

複習完前三篇的主要內容就可以開始進行下一步的測試學習了. 

參考 :