2021年4月11日 星期日

p5.js 學習筆記 (二) : 基本繪圖 (上)

布置好 p5.js 的執行環境後就可以開始在畫布上繪圖了, 除了繪圖與互動應用, 其實透過 p5.js 來學習 Javascript 非常有效, 因為它可以將學習轉化成玩, 玩的樂趣無形中會沖淡學習必有的痛苦. 本系列之前的文章參考 :



1. 網頁模板  : 

以下測試將使用上一篇 "環境配置" 中的 CDN 網頁模板 : 

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      }
    function draw() {
      background(200, 200, 200);
      }
  </script>
</body>
</html>

p5.js 的程式架構很簡單, 只有兩個函式 (與 Arduino 的程式架構類似), 由只執行一次的 setup() 與預設每秒執行 60 次的 draw() 函式組成, 所以初始設定的指令碼要放在 setup() 內, 而需重複執行的指令碼則放在 draw() 內. 上面的模板網頁中, 如果畫布的背景不需要改變, 則可將 background() 移到 setup() 內. 


2. 偵錯工具 console.log() : 

p5.js 為 Javascript 網頁應用程式的互動繪圖函式庫, 程式開發與結果驗證都是在瀏覽器上, 開發中的偵錯除錯主要是利用 Javascript 的 console.log() 將變數輸出到瀏覽器的控制台觀察變數之值以資判斷, 在 Chrome 瀏覽器上按 F12 (筆電要同時按 Fn 鍵) 會在頁面右邊顯示開發介面, 切到 Console 頁籤即可下達 Javascript 指令 :




例如下面的範例是呼叫 p5.js 的內建函式 frameRate() 取得 draw() 的迴圈頻率, 然後再用 console.log() 輸出此頻率值:



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 200);
      }
    function draw() {
      background(100);
      console.log(frameRate());      //輸出 draw() 的迴圈頻率預設值
      }
  </script>
</body>
</html>

結果如下 :




可見 draw() 大約每秒會被呼叫 60 次左右. 


3. 內建常數, 變數, 與函式 : 

撰寫 sketch 程式時除了會用到 Javascript 本身的常數, 變數, 與函式外, p5.js 函式庫也提供了很多與繪圖相關的常數, 變數, 與函式, 只要熟悉這些內建函式的呼叫方式與常數變數所代表的意義, 就能用少許的程式碼在畫布上繪圖或產生互動效果. 以下摘要整理常用的常數, 變數, 與函式 : 


常用的內建常數如下表 :

 p5.js 內建常數 說明
 PI 圓周率, 約 3.1415926
 HALF_PI 半圓周率, 約 1.5707963
 QUARTER_PI 四分之一圓周率, 約 0.7853982
 TWO_PI 兩倍圓周率, 約 6.2831853
 DEGREES 角度, 作為 angleMode() 函式之參數, 設定為角度模式
 RADIANS 弧度, 作為 angleMode() 函式之參數, 設定為弧度模式
 CENTER 前兩個參數為圓心, 後兩個參數為寬度 w 與高度 h (或置中對齊)
 RADIUS 前兩個參數為圓心, 後兩個參數為寬度半徑 w/2 與高度半徑 h/2
 CORNER 前兩個參數為左上角座標, 後兩個參數為寬度 w 與高度 h
 CORNERS 前兩個參數為橢圓外方框一角之座標, 後兩個參數為對角座標
 LEFT 向左對齊 (水平, 垂直)
 RIGHT 向右對齊 (水平)
 BOTTOM 向下對齊 (垂直)
 CLOSE 傳入 endShape() 會使形狀之終點與起點相連成封閉圖形


常用的內建變數如下表 :

 p5.js 內建變數 說明
 width 畫布寬度 (px)
 height 畫布高度 (px)
 frameCount draw() 的累計呼叫次數
 mouseIsPressed 滑鼠左鍵是否被按下 (true/false)
 mouseX 滑鼠的 X 座標
 mouseY 滑鼠的 Y 座標


常用的內建函式如下表 :

 p5.js 內建函式 說明
 createCanvas(w, h) 以網頁左上角為起點建立寬 w, 高 h 之畫布 (單位 px)
 background(R [, G, B]) 設定畫布背景色 (值 0~255), 只傳入一個參數時表示 R=G=B
 fill(R [,G, B]) 設定封閉圖形之填滿顏色直到呼叫 noFill() 為止
 noFill() 取消上一次的 fill() 填滿設定
 stroke(R [, G, B]) 設定線條或封閉圖形框邊顏色
 noStroke() 取消上一次 stroke() 所設定之邊框顏色
 strokeWeight(px) 設定線條或封閉圖形框邊寬度 (px)
 point(x, y [, z]) 在座標上繪製一個點, 顏色大小用 stroke() 與 strokeWeigth()
 line(x1, y1, x2, y2) 在座標 (x1, y1) 與 (x2, y2) 間繪製直線
 quad(x1,y1,x2,y2,x3,y3,x4,y4) 用 (x1,y1), (x2,y2), (x3, y3), (x4,y4) 作頂點繪製四邊形
 square(x, y, s [, r]) 在 (x, y) 座標繪製邊長 s 的正方形 (r=圓角半徑)
 triangle(x1,y1,x2,y2,x3,y3) 用 (x1,y1), (x2,y2), (x3, y3) 作頂點繪製三角形
 rect(x, y, w [, h]) 繪製一個左上角座標為 (x, y), 寬 w 高 h 的矩形 (CENTER 模式)
 rectMode(mode) 設定矩形繪圖函式 rect() 的參數模式, CENTER=(x, y, w, h)
 ellipse(x, y, w [, h]) 繪製圓心為 (x, y), 寬徑 w, 高徑 h 之橢圓 (w=h 時為正圓)
 ellipseMode(mode) 設定橢圓函式 ellipse() 的參數模式, 預設 CENTER=(x, y, w, h)
 arc(x, y, w, h, start, stop) 繪製圓心為 (x, y), 寬徑 w, 高徑 h, 起訖弧度 start~stop 之弧形
 textSize(size) 設定字型大小 (px)
 textAlign(halign [,valign]) 文字對齊 halign=LEFT/CENTER/RIGHT valign=TOP/BOTTOM
 text(str, x, y) 在座標 (x, y) 處開始輸出字串 str
 beginShape([kind]) 開始記錄形狀 kind (LINES/QUADS/TRIANGLES 等) 之頂點
 vertex(x, y [, z]) 用來在 beginShape() 與 endShape() 之間指定頂點座標 
 endShape([CLOSE]) 終止紀錄頂點 (傳入 CLOSE 會連接起點與終點形成封閉形狀)


事實上 p5.js 所提供的函式非常豐富, 但上面所列的常用常數, 變數, 與函式已足夠來作測試了, 完整的 p5.js 說明文件參考 :



5. 顏色設定 : 

在 p5.js 中用來設定顏色的函式主要有下面五個 : 
  • background(R [,G, B, alpha]) : 畫布的背景色
  • stroke(R [,G, B, alpha]) : 線條或圖形之邊框顏色
  • noStroke() : 取消前一次的 stroke() 設定
  • fill(R [,G, B, alpha]) : 圖形內之填滿顏色
  • noFill() : 取消前一次的 fill() 設定
這些函式的參數型態其實有多種模式, 通常 RGB 三原色模式較常用, 備選的第四參數 alpha 可設定顏色的透明度, 其值為 0 (不透明) ~ 255 (全透明). 

顏色色碼可用 Mozilla 的顏色選擇器來選定 : 


另外一個常用的顏色指定法為傳入 CSS 的顏色字串, 例如 :

background('red');
fill('ivory');
stroke('cyan'); 

完整的 CSS 顏色名稱字串參考 :



6. 用 point() 繪製點 : 

呼叫 point() 函式可繪製一個點, 預設大小是一個 1px 的點, 其顏色與大小可分別用 stroke() 與 strokeWeight() 設定, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 200);      
      background(0, 0, 0);    //背景=黑色
      }
    function draw() {
      stroke(255, 255, 0);     //設定顏色=黃色
      strokeWeight(5);          //設定大小=5px
      point(100, 100);           //在 (100, 100) 繪製一個點
      }
  </script>
</body>
</html>

此例在 setup() 中建立一個 200*200 px 的畫布, 並設定背景色為黑色, 然後在 draw() 迴圈函式中先用 stroke() 設定前景色為黃色, 用 strokeWeight() 設定畫筆粗細為 5px, 最後呼叫 point() 在 (100, 100) 座標處繪製一個點, 結果如下 :



可見在畫布中央繪製了一個黃點. 


7. 用 line() 繪製線 : 

呼叫 line(x1, y1, x2, y2) 可以在畫布的兩個坐標點 (x1, y1) 與 (x2, y2) 之間繪製一條直線, 例如 :


<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 200);      
      background(0, 0, 0);
      }
    function draw() {
      stroke(255, 255, 0);       //前景色=黃色
      strokeWeight(2);            //畫筆=2px
      line(0, 0, 200, 200);       //左上至右下畫一直線
      stroke(255, 255, 255);   //前景色=白色
      strokeWeight(6);            //畫筆=6px
      line(200, 0, 0, 200);       //右上至左下畫一直線
      }
  </script>
</body>
</html>

此例先設定前景色為黃色畫筆為 2px, 然後呼叫 line() 於 (0, 0) 與 (200, 200) 間繪製直線 (左上到右下); 然後更改前景色為白色, 畫筆改為 5px, 再次呼叫 line() 於 (200, 0) 與  (0, 200) 間繪製直線 (右上到左下), 結果如下 : 




8. 用 quad() 繪製四邊形 : 

呼叫 quad(x1, y1, x2, y2, x3, y3, x4, y4) 函式繪製四邊形必須傳入四個頂點的坐標 : (x1, y1), (x2, y2), (x3, y3), (x4, y4), 連接此四點即為四邊形, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      }
    function draw() {  
      fill('red');           //設定填滿顏色為紅色
      quad(38, 31, 86, 20, 69, 63, 30, 76);    //繪製四邊形
      noFill();            //取消上一次填滿顏色設定
      fill('yellow');     //設定填滿顏色為黃色
      quad(128, 45, 170, 34, 180, 85, 158, 88);     //繪製四邊形
      noFill();            //取消上一次填滿顏色設定
      }
  </script>
</body>
</html>

此例繪製了兩個四邊形, 同時利用 CSS 顏色字串呼叫 fill() 來填滿四邊形內的顏色, 注意, 雖然可以直接設定新的填滿顏色, 但先用 noFill() 取消設定是個好習慣, 結果如下 :




9. 用 square() 繪製正方形 : 

呼叫 square(x, y, s [, r]) 繪製正方形需傳入左上角頂點坐標 (x, y) 與邊長 s, 若傳入備選的第四參數 r 則可將正方形圓角化, 此第四參數為圓角之半徑 (px), 例如 : 


測試 5 : 繪製正方形 [看原始碼]  

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      }
    function draw() {  
      fill('cyan'); 
      square(25, 25, 50);           //繪製正方形
      noFill();
      fill('gold');
      square(125, 25, 50, 10);    //繪製有 10px 圓角之正方形
      noFill();
      }
  </script>
</body>
</html>

此例繪製了兩個正方形, 其中第二個使用第四參數指定了半徑為 10 px 之圓角, 結果如下 :




2021年4月9日 星期五

p5.js 學習筆記 (一) : 環境配置

今天在 blogger 中發現前年 (2018/12/6) 未寫完的 p5.js 函式庫的草稿, 花了點時間整理完後, 覺得這個開源專案很有趣, 有很多玩家是藝術界人士, 他們並非網頁或 IT 技術背景出身, 但卻能用 p5.js 開發出令人讚嘆的作品, 可見 p5.js 是一個非常容易上手的網頁多媒體互動工具, 所以今天打鐵趁熱, 繼續來測試.  

p5.js 好用好學的原因是開發者將許多 Javascript 與繪圖所需的物理數學細節都封裝在好用的函式中, 只要用文字編輯軟體開啟一個載入 p5.js 函式庫的網頁檔, 撰寫 Javascript 程式碼呼叫 p5.js 的函式即可在網頁上製作圖像, 動畫, 以及互動應用程式, 成果發布到網站就可以讓全世界的人用瀏覽器觀賞, 不須安裝任何製作軟體. 


1. 下載 p5.js 函式庫 :    

如果要在本機環境下測試 p5.js, 需到官網下載 p5.js 函式庫 :


在下載頁底下按中間的 "p5.min.js" 鈕下載壓縮後的函式庫 (約 795 KB), 若要研究或改寫原始碼也可下載左邊未壓縮的 p5.js (約 4.2 MB) :




將下載的 p5.min.js 放在工作目錄下, 然後開啟文字編輯器 (記事本, EditPlus 或 NotePad++), 建立如下測試網頁模板, 與 p5.min,js 同樣存放於工作目錄下 (用 utf-8 編碼存檔) :

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {    //只執行一次
      createCanvas(400, 300);   //(x, y)
      } 
     
    function draw() {     //迴圈函式, 預設每秒執行 60 次
      background(200, 200, 200);    //(R, G, B)
      }   
  </script>
</body>
</html>

此處內嵌於網頁中的兩個 Javascript 函式就是 p5.js 的繪圖函式, 其中 setup() 只會執行一次, 而 draw() 則會被 p5.js 以預設每秒 60 次的頻率持續被呼叫, 也是 p5.js 最主要的繪圖程式, 這與 Arduino 的 setup() 與 loop() 功能是一樣的. 

此模板網頁的 setup() 函式中呼叫 p5.js 的 createCanvas() 函式在網頁中建立一個 400px*300px 大小的畫布, 然後進入 draw() 迴圈呼叫 background() 函式將畫布背景顏色設為 (R, G, B)=(200, 200, 200), 其中 R/G/B 為三原色色碼, 範圍為 0~255. 將此模板存檔為 p5.js_template_1.htm, 以瀏覽器開啟結果如下 : 




可見在網頁左上角開始的位置出現了一塊 400*300 的灰色畫布. 也可以將網頁與 Javascript 程式完全分離, 把 setup() 與 draw() 寫在另一個 Javascript 檔案 (副檔名 .js, 例如 sketch.js), 然後放在 p5.js 後面匯入網頁中 :

//sketch.js
function setup() {    //只執行一次
  createCanvas(400, 300);   //(x, y)
  } 
     
function draw() {     //迴圈函式, 預設每秒執行 60 次
  background(200, 200, 200);    //(R, G, B)
  }   

這時網頁要修改為 :

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="p5.min.js"></script>
  <script src="sketch.js"></script>
</head>
<body>
</body>
</html>

結果與上面是一樣的. 


2. 使用 CDN 提供的函式庫 : 

如果網頁是要放在公開的伺服器上, 則可以直接使用 CDN 網站所提供的 p5.js 函式庫, 這樣就不需要下載函式庫自備, 按下載網頁右邊的 "CDN" 按鈕會顯示函式庫的 CDN 網址 :





通常使用 p5.min.js, 按右邊的鍊條複製其 URL :

https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js

然後修改上面的模板網頁, 用此 URL 替換 script 標籤中的 src 屬性值 :

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      }

    function draw() {
      background(200, 200, 200);
      }
  </script>
</body>
</html>

將此網頁以 utf-8 編碼存檔為 p5.js_template_cdn.htm, 然後用瀏覽器開啟就可以看到與上面相同的網頁. 也可以上傳到伺服器, 例如 GitHub 的個人網站上, 例如 :



3. 使用 p5.js 線上編輯器 : 

如果只是測試學習, 最方便的工具是 p5.js 官網提供線上編輯器, 其網址為 : 




線上編輯器使用網頁與程式分開的結構, 左上角編輯框用來編輯名為 sketch.js 的 Javascript 程式, 預設已經打上 setup() 與 draw() 兩個函式, 直接在此兩函式內輸入程式碼, 然後按 Play 鍵就能在右方的 preview 預覽區看到效果. 

按預覽區右上方的 Signup 鈕加入會員可自訂編輯器屬性 (例如布景, 字型大小等), 也可將程式儲存在雲端 :








也可以將自己的 sketch 程式分享 (按 share), 例如下面這個可用滑鼠操控頻率的範例 :


下面範例修改自官方教學文件 :


<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 200, WEBGL);
      }
    function draw() {
      background(0);
      noFill();
      stroke(100, 100, 240);
      rotateX(frameCount * 0.01);
      rotateY(frameCount * 0.01);
      box(90, 90, 90);
      }
  </script>
</body>
</html>

結果如下 :



Processing 的網頁版函式庫 ps.js

2018 年底我在一本 Arduino 的書上看到用 Processing 語言顯示感測器資料的介紹, 在搜尋相關資料時找到 p5.js 函式庫, 這是 Processing 的 Javascript 網頁版本, 因為 Processing 本身是 Java 寫的, 所以使用 Processing 顯示Arduino 蒐集的感測器資料必須使用 Java 語法, 移植到 Javascript 後就可以在網頁上繪圖, 製作動畫或遊戲了, 不再需要 Java Applet 或 Flash 外掛, 這真的太棒了! 

p5.js 的前身是 John Rezig (jQuery 之父) 於 2008 年開發的 Processing.js 開放原始碼函式庫, 此函式庫用來在瀏覽器上顯示圖表與互動內容, 但此專案已在 2018 年底停止繼續開發了. 美國藝術家與電腦科學家 Lauren McCarthy 獲得 Processing 基金會的官方支援, 以 Processing.js 的成果為基礎於 2013 年開發了 p5.js 作為 Processing.js 的繼承者, 其命名源自 Processing 最早的網域名稱 proce55ing (因為 processing.org 當時已被註冊), 參考 :


p5.js 的官網與 GitHub 原始碼寄存參考 :


目前市面上關於 p5.js 的書籍不多, 只有如下數本 : 



Source : 博客來


此書由淺入深循序漸進, 透過 p5.js 來學習 Javascript 語法, 是很不錯的入門書.



Source : 博客來


此書是 p5.js 開發者 Lauren McCarthy 所著, 著重於利用演算法製作數學或藝術圖形, 例如碎形, 葉片等等, 需要一些代數, 超越函數等數學基礎. 



Source : 博客來


此書也是 p5.js 的藝術應用, 屬於實用級. 




Source : Amazon


此書似乎連 Amazon 都買不到. 

參考 : 


2021年4月6日 星期二

Python 學習筆記 : 基本語法 (五) : 錯誤與例外

本篇繼續來複習整理 Python 基本語法之例外處理, 本系列之前的文章參考 :


參考書籍 :
  1. 人工智慧 Python 基礎課 (碁峰, 陳會安)
  2. 精通 Python (碁峰, 賴屹民譯)
  3. Python 程式設計入門與運算思維 (新陸, 曹祥雲)
  4. 一步到位! Python 程式設計 (旗標, 陳惠貞)
  5. Python 程式設計入門指南 (碁峰, 蔡明志譯)
  6. Python 也可以這樣學 (博碩, 董付國)

十一. 例外處理 :   

程式開發過程中會遇到下列三種錯誤 : 


 程式的三種錯誤 偵錯難易 說明
 語法錯誤 容易 違反語法規則, 例如縮排, 關鍵字拼寫等.
 執行時期錯誤 (例外) 容易 程式符合語法, 但執行時出現錯誤, 例如除以 0 等.
 邏輯錯誤 困難 程式符合語法, 執行時也無例外出現, 但結果不正確.


有語法錯誤的程式無法順利執行, 直譯器會顯示錯誤原因並指出錯誤之程式碼位置, 因此這種錯誤很容易即能除錯. 

有些程式語法並無問題, 但有時可順利執行, 有時卻突然終止執行 (閃退) 並顯示錯誤訊息, 通常是因為不同的輸入條件或中間運算結果所致, 此種錯誤稱為執行時期錯誤, 又特稱為例外 (exception), 其除錯因為有錯誤訊息因此也不難, 但能否偵錯卻要靠測試條件是否能充分觸發可能之例外而定. 

邏輯錯誤又稱語意錯誤 (semantic error), 存在邏輯錯誤的程式雖均可順利執行, 但執行結果卻不正確 (錯誤), 或者令程式陷入無窮迴圈, 此種錯誤來自程式設計師的思考邏輯不周全或演算法錯誤, 邏輯錯誤很難偵錯與除錯. 
 

1. 語法錯誤 :    

語法錯誤包括 SyntaxError, KeyError, IdentationError, IndexError, NameError, 與 AttributeError 等, 出現語法錯誤時必須修改程式碼使其符合 Python 語法規則後才能順利執行, 常見的語法錯誤列舉如下 : 
  • 使用關鍵字當識別字 (變數, 函式, 類別之名稱)
  • 關鍵字拼寫錯誤
  • 程式區塊忘記冒號直接跳行
  • 同一個區塊內之縮排不一致
  • 字串的括號不成對或不匹配 (例如前面用單引號, 後面用雙引號)
  • 元組 (), 串列 [], 字典 {} 內的元素或項目沒有用逗號隔開
  • 虛數使用了數學的 i, 在 Python 應該用 j 表示虛數
  • 存取字串, 元組, 串列時索引超出範圍 (IndexError)
  • 存取字典時所用之鍵不存在 (KeyError)
  • 存取不存在的物件屬性或方法 (AttributeError)
  • 存取目前命名空間中不存在的變數 (NameError)
參考 :


例如 :  

>>> else=123                          # else 是關鍵字, 不可以用作識別字
  File "<pyshell>", line 1
    else=123
       ^
SyntaxError: invalid syntax   
>>> Else=123                          # Else 不是關鍵字, 是合法識別字
>>> Else
123
>>> for i In range(1, 10):        # 關鍵字拼寫錯誤 (In 應為 in)
    print(i)
    
  File "<pyshell>", line 1
    for i In range(1, 10):
           ^
SyntaxError: invalid syntax
>>> a=10
>>> if a < 0:       
    print('negative')
elseif a == 0:                           # 關鍵字拼寫錯誤 (elseif 應為 elif)
    print('zero')
else:
    print('positive')
    
  File "<pyshell>", line 3
    elseif a == 0:
           ^
SyntaxError: invalid syntax
>>> def foo()                           # 函式標頭應以冒號結束, 開啟內縮區塊 (缺了冒號)
   print('bar')
   
  File "<pyshell>", line 1
    def foo()
            ^
SyntaxError: invalid syntax
>>> def foo(a):
    print(a)
        return a+1                        # 區塊內縮不一致 (return 應與 print 齊頭)
    
  File "<pyshell>", line 3
    return a+1
    ^
IndentationError: unexpected indent
>>> print("Hello World)         # 字串的括號不成對
  File "<pyshell>", line 1
    print("Hello World)
                      ^
SyntaxError: EOL while scanning string literal
>>> print('Hello World")       # 字串的括號前後不一致
  File "<pyshell>", line 1
    print('Hello World")
                       ^
SyntaxError: EOL while scanning string literal
>>> a=[1, 2, 3  4]
  File "<pyshell>", line 1
    a=[1, 2, 3  4]
                ^
SyntaxError: invalid syntax
>>> a=1+2i                             # 在 Python 中以 j 表示虛數, 不是數學中慣用的 i 
  File "<pyshell>", line 1
    a=1+2i
         ^
SyntaxError: invalid syntax
>>> "tony"[4]                         # 索引超出範圍 (此例索引 0~3)
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
IndexError: string index out of range
>>> {'foo':1, 'bar': 2}['tony']   # 字典的鍵不存在
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
KeyError: 'tony'
>>> "tony".length                   # 字串物件無 length 屬性
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
AttributeError: 'str' object has no attribute 'length'


2. 邏輯錯誤 : 

邏輯錯誤來自程式設計師本身的疏忽, 思考邏輯不正確, 或解決問題的演算法有誤, 此種錯誤很難偵錯, 因為程式都能順利執行不會有任何錯誤訊息, 只是結果不正確, 但有時也無法從結果看出正確與否, 只能一行一行檢視原始碼仔細推敲才可能發現錯誤之處. 

導致邏輯錯誤的可能原因如下 :
  • 使用了錯誤的變數導致運算式結果不正確
  • 誤用了整數除法使得小數部分被略去
  • 忽略了 0 起始或 1 起始差異導致差一錯誤 (off-by-one error), 例如迴圈終止值設定
  • 忽略了運算子的優先順序導致運算式得出錯誤結果
  • 搞錯了縮排的層次
例如計算 1+2+3+4+5 之和可用 for 迴圈搭配 range(from, to) 函式, 但要注意 range() 傳回的可迭代序列事實上只到 to-1 而非 to, range(1, 5) 傳回的序列為 1, 2, 3, 4, 所以得到的結果是錯的 :

>>> sum=0
>>> for i in range(1, 5):    # 這只加到 4 而已 : 1+2+3+4
    sum += i
    
>>> print(sum)                  # 1+2+3+4+5=15, 故計算結果錯誤
10


3. 執行時期錯誤 (例外) :    

程式語法正確, 但執行中因為無法順利處理資料而發生的異常錯誤稱為例外 (exception), 將導致程式崩潰而終止執行並輸出錯誤訊息, 但這些技術性資訊通常會使用者丈二金剛摸不著頭腦, 為了避免這種情況, 程式設計師必須捕捉這些可能的例外並做適當處理以增進程式的強固性. 

常見的執行時期錯誤例如 :
  • 除以零
  • 將字串與數值相加
  • 呼叫有必要參數之函式時未傳入引數
  • 欲開啟之檔案不存在
  • 取得網路文件時連線中斷
其中有些可能的錯誤只要例外處理最常用於程式需要與外部交換資料的時候, 例如讀寫外部檔案或從網路下載資料等, 這些操作都有環境不確定因素 (檔案不存在或網路連線異常), 其它可用因此必須程式設計師必須捕捉這些可能之例外並加以處理, 否則程式會因異常而突然終止, 使用者會對這些情況感到不知所措. 

除了開發時期程式員本身常遇到外, 軟體上線後也常因為以 input() 或 GUI 介面從使用者輸入取得之資料運算時造成例外, 故開發時需注意此類 Bug. 

語法錯誤與執行時期錯誤這兩種錯誤都有提示訊息可用做偵錯的參考, 而邏輯錯誤是最難被偵錯的, 因為問題出在對程式處理邏輯的想法不正確所致, 有時也出於程式員誤用運算子, 例如在條件式中使用了 = 運算子做比較而非 ==, 這不會出現語法錯誤, 因為在條件式中用 = 進行指派總是傳回 True. 


(1). 例外處理類別 :   

Python 提供了以 BaseException 為根類別的例外處理類別, 用來處理被拋出了各式例外, 常見的例外類別繼承關係如下圖所示 : 





BaseException 類別是 Python 的內建類別 (可匯入 builtins 後用 dir 檢視), 也是所有例外類別的最上層父類別. 程式設計師除了直接使用這些內建之例外類別來捕捉例外, 也可以自訂例外類別, 但自訂例外類別時並非直接繼承 BaseException, 而是應該繼承其子類別 Exception, 參考 :


Python 的例外處理語法為如下之 try except 結構 (try-and-error 模式) : 

try: 
    可能觸發例外之程序
except 例外類別名稱1 [as 別名1]: 
    例外處理程序1
except 例外類別名稱2 [as 別名2]: 
    例外處理程序2
.....
except:
    所有其他例外處理程序
[else:
    沒有觸發任何例外時之處理程序]
[finally:
    不論有無觸發例外必定執行之程序]

說明如下 : 
  • try 與至少一個 except 區塊是必須的, else 與 finally 則是可有可無的. 
  • 將可能發生例外的程式碼放在 try 區塊中, 一旦發生執行時期錯誤就會停止 try 區塊內程序的執行並拋出例外, 此例外會由底下的 except 區塊依序捕捉, 首先由排在前面的指名例外類別捕捉, 一旦捕捉到一個例外類別後, 其它的例外類別就不會再嘗試捕捉. 如果前面的指名例外類別都沒有捕捉到此例外, 就由最後面的 except 區塊概括承受全部捕捉. 
  • 若沒有發生例外, 則 try 區塊程序執行完畢後會執行 else 區塊, 不管有無發生例外, finally 區塊都會被執行, finally 通常用來處理資源清理工作, 釋放 try 區塊中所占用之記憶體資源, 例如關閉檔案等.
  • 如果 try 區塊發生之例外沒有被任何一個 except 區塊捕捉到, 或者連 else 區塊也發生例外, 則這些例外會等 finally 區塊執行完畢後才被拋出, 亦即發生例外並不會影響 finally 區塊的程序, 它一定會被執行. 因此若在函式中使用了異常處理結構, 切勿在 finally 區塊中使用 return 傳回結果, 因為這會讓該函式不管執行結果如何都傳回相同結果, 變成一個難以發現的邏輯錯誤. 
  • 例外類別名稱可以用 as 關鍵字取別名 (例如 e) 以節省打字長度, 在例外處理程序中可直接呼叫 print(e) 輸出例外物件內容, 也可以用 print(e.args) 檢視, args 屬性值為一個包含例外原因的 tuple.   
 此語法結構的演算法可用下圖表示 :




如果某些例外要共用相同的處理程序, 則可將這些例外放在 tuple 中, 結構如下 :

try: 
    可能觸發例外之程序
except (例外類別名稱a1,  例外類別名稱a2, ... ) [as 別名a]: 
    共用的例外處理程序a
except (例外類別名稱b1,  例外類別名稱b2, ... ) [as 別名b]: 
    共用的例外處理程序b
except:
    其它例外處理程序
else:
    沒有觸發任何例外時之處理程序
finally:
    不論有無觸發例外必定執行之程序

參考 : 


例如 : 

>>> try:
    a,b=input("請輸入被除數與除數(以空格隔開之整數)").split()
    a=int(a)
    b=int(b)
    print("被除數/除數=", a/b)
except ZeroDivisionError as e:
    print("發生除以零例外:", e)
    print(e.args)
else:
    print("沒有發生例外")

請輸入被除數與除數(以空格隔開)10 2
被除數/除數= 5.0
沒有發生例外

請輸入被除數與除數(以空格隔開之整數)10 0        # 除以零
發生除以零例外: division by zero
('division by zero',)

此例透過 input() 讓使用者輸入以空格隔開的兩個整數 a, b, 然後計算 a 除以 b, 當除數 b 不為零時不會發生例外, 當 b 為 0 時則拋出 ZeroDivisionError 的例外被 except 捕捉. 注意, 發生例外時的那行程式碼與以下的程式碼都會被終止, 故不會輸出 "被除數/除數=" 字串. 

但上面的程式只捕捉了 ZeroDivisionError 這個例外, 如果輸入資料中有一個為非數值將使程式於 try 區塊拋出另外一個例外 ValueError, 但因為程式沒有捕捉此例外因此會導致異常終止 : 

請輸入被除數與除數(以空格隔開)123 abc           # 輸入非數值觸發了未被捕捉之例外
Traceback (most recent call last):
  File "<pyshell>", line 4, in <module>
ValueError: invalid literal for int() with base 10: 'abc'

程式應捕捉所有可能出現的例外, 因此添加捕捉 ValueError 例外如下 : 

>>> try:
    a,b=input("請輸入被除數與除數(以空格隔開)").split()
    a=int(a)
    b=int(b)
    print("被除數/除數=", a/b)
except ZeroDivisionError as e:
    print("發生除以零例外:", e)
except ValueError as e:     
    print("發生值的例外:", e)
else:
    print("沒有發生例外")

請輸入被除數與除數(以空格隔開)123 abc       # 拋出的 ValueError 被捕捉
發生值的例外: invalid literal for int() with base 10: 'abc'

也可以將這兩個例外以 tuple 組合在一起合併處理, 例如 :

>>> try:
    a,b=input("請輸入被除數與除數(以空格隔開)").split()
    a=int(a)
    b=int(b)
    print("被除數/除數=", a/b)
except (ZeroDivisionError, ValueError):              # 合併處理兩個例外
    print("發生例外")
else:
    print("沒有發生例外")

請輸入被除數與除數(以空格隔開)10 0             # 發生除以零例外
  發生例外

請輸入被除數與除數(以空格隔開)123 abc        # 發生值的例外
  發生例外                                                            

若這樣攏統, 也可以用內建函式 isinstance() 來分辨例外類別, 例如 : 

>>> try:
    a,b=input("請輸入被除數與除數(以空格隔開)").split()
    a=int(a)
    b=int(b)
    print("被除數/除數=", a/b)
except (ZeroDivisionError, ValueError) as e:      # 合併處理兩個例外
    if isinstance(e, ZeroDivisionError):                # 用 isinstance() 分辨例外類別
        print("第二個整部不可為 0")
    else:
        print("請輸入兩個以空格隔開的整數")
else:
    print("沒有發生例外")

請輸入被除數與除數(以空格隔開)10 0   
第二個整部不可為 0

請輸入被除數與除數(以空格隔開)123 abc   
請輸入兩個以空格隔開的整數

2021年4月5日 星期一

遊岡山之眼

因為沒去過岡山之眼, 恰好菁菁說六月底之前免門票 (全票 60 元), 趁著連假最後一天就去看看唄, 順便訂了五點的岡山逐鹿炭烤.  下午兩點出發走國一至燕巢下交流道, 至阿公店水庫管理局轉搭高雄客運的接駁車 (整點與 30 分一班, 但滿 19 人即開車), 管理局免費停車, 停好就到旁邊候車. 這停車場很有特色, 上面是太陽能板, 既可發電又能給車子遮陰. 

登上瞭望台之前有個賣飲料冰品食物的小市集, 霜淇淋三支 100 元還蠻濃郁的. 走過一連串之字形步道後就到達岡山之眼的瞭望台, 站在上面可以俯瞰大半個阿公店水庫, 但上面風大, 戴帽子要小心別被吹落. 菁菁主要是去拍照的, 我則主要是看風景. 如果沒有在上面駐足太久的話, 其實 30 分鐘內就走完了. 

離開岡山之眼回到水庫開車前往岡山的逐鹿炭烤, 據說高雄市區當天訂幾乎都訂不到, 岡山還蠻好訂的. 但炭烤大多是肉, 雞肉, 豬肉, 蝦, 魚肉, 牛肉 .... 菁菁一直烤給我吃, 不到三十分鐘我已覺得有點飽了. 雖然我比較喜歡火鍋勝過炭烤, 但這家整體來說是很不錯的餐廳, 服務態度尤其親切. 

好文 : 終於要上雲端了嗎?

今天突然想到 Google App Engine (GAE, 現已納入 GCP 中) 是否已支援 Python 3? 馬上查谷歌大神, 果然有了, 同時找到一篇很不錯的文章, 介紹如何將網頁專案 (Django/Flask) 佈署在 GCP : 


這篇文章特地提醒使用者在註冊 GCP 使用其雲端服務時首先要設定自己的預算上限, 否則一旦你的網路服務出奇地大受歡迎, 暴增的流量會讓你看到帳單說不出話來. 

這篇文章是作者一系列 "不做怎麼知道" 系列中的一篇, 完整目錄參考 :

 
作者透過在 iT 邦幫忙一天寫一篇自己在不熟悉領域的學習心得來逼自己向前進, 嗯, 這構想很有意思, 先記下來, 有空再回頭好好來施展吸星大法. 

我已多年未玩 GAE, 自從被納入 GCP 後, 更覺得麻煩, 不像以前 GAE 免費用時代方便. 但是檢視自己之前在 GAE 上佈署的測試網頁發現它們居然還活著耶! 谷歌就這個好處, 不像那些免費虛擬主機, 流量太高說你 abuse, 流量太低又給你砍帳號. 

2021年4月4日 星期日

2021 年第 14 周記事

本周與下周因為清明節連假補假關係都只上四天班, 但周五第一天假在花蓮清水隧道居然發生火車撞卡車導致嚴重死傷事故, 這幾天看了新聞報導真的很心酸, 有帶孩子快樂出遊的, 有趕回家鄉掃墓的, 有放假回家看家人的, 都被可惡的漫不經心給摧毀, 受害者家屬的人生從彩色變黑白, 這種傷口也許會隨時間而癒合, 但人生的遺憾卻會帶上一輩子. 害這麼多人這樣, 這罪過可不輕啊!

我週五晚上從鄉下回高雄時, 在離家不遠處的第一個路口停下等紅燈, 從後照鏡注意到後面那台車看到我停下便打左方向燈越過雙黃線超車直接闖紅燈揚長而去, 一點都沒有羞恥心, 或許還笑我是傻瓜, 這種鄉下停甚麼紅燈, 沒車就過啦! 或許惡政先生說得也有點道理, 這個國家上上下下都有問題, 但我認為無羞恥心應該擺第一. 

本周暫時停下網頁爬蟲的學習, 回頭複習整理 Python 基本語法, 以前都是邊看邊用, 沒有扎扎實時去學基礎的東西, 例如例外, 檔案讀寫, 正規式等等, 希望這次能利用整理筆記的機會重頭再複習一遍. 清明連假原本應該會留在鄉下好好看書, 但菁菁說想去七股鹽田與岡山之眼看看, 所以我周五回去, 週六回來, 今天行程式中午出發去七股. 

七股鹽山以前二哥與菁菁幼稚園與小學戶外教學曾去過, 十幾年之後重遊居然忘了當時的時空場景, 努力從四周建物回想, 終於拼湊出那時幫二哥排隊買鹹蛋黃冰, 陪菁菁爬上鹽山, 以及在廟口吃便當的情景, 歲月不饒人, 新手爸爸已然變成中年大叔. 看到菁菁偷拍我在瓦盤鹽田涼亭打盹的模樣 (說像財神爺), 不禁幻想撿到一個奇怪的相機, 在好奇按下快門之後突然就回到 35 歲時的模樣 ....  (韓劇是不是看太多了).

2021年4月2日 星期五

市圖還書 1 本 (Python 機器學習)

本周市圖還下列書籍 : 
這本有人預約, 目前沒時間看先還, 此書是第二版 (母校也有此書), 我有買封面很像的 "Python 深度學習" (也是博碩出版, 劉立民翻譯), 但這本我沒買, 用借的就好, 此書主要講 Scikit-learn 跟 Tensorflow ( v1).

2021年4月1日 星期四

好聽的 "海底"

這兩天菁菁又有了喜愛的新歌 "海底", 乍聽有古典風味道, 細聽又覺得像韓劇 Voice 片尾曲, 旋律好美, 歌手獨特的嗓音聽來很有療癒感 : 





可是當我仔細看了歌詞, 卻又覺得詞意充滿著淒涼, 悲觀, 與灰色 : 

作詞作曲 : PSROSIE 

散落的月光穿過了雲
躲著人羣
鋪成大海的鱗
海浪打溼白裙
試圖推你回去
海浪清洗血跡 妄想溫暖你
往海的深處聽 誰的哀鳴在指引
靈魂沒入寂靜 無人將你吵醒
你喜歡海風鹹鹹的氣息
踩著溼溼的沙礫
你說人們的骨灰應該撒進海裡
你問我死後會去哪裡
有沒有人愛你
世界能否不再 總愛對涼薄的人扯著笑臉
岸上人們臉上都掛著無關
人間毫無留戀 一切散為煙
散落的月光穿過了雲
躲著人群溜進海底
海浪清洗血跡 妄想溫暖你
靈魂沒入寂靜 無人將你吵醒
你喜歡海風鹹鹹的氣息
踩著溼溼的沙礫
你說人們的骨灰應該撒進海裡
你問我死後會去哪裡
世界已然將你拋棄
總愛對涼薄的人扯著笑臉
岸上人們臉上都掛著無關
人間毫無留戀 一切散為煙
來不及 來不及
你曾笑著哭泣
來不及 來不及
你顫抖的手臂
來不及 來不及
無人將你打撈起
來不及 來不及
你明明討厭窒息

參考 : 

对一支榴莲的《海底》歌词的理解

歌手 "一支榴槤" 是誰?

高科大還書 2 本 (Python 網路爬蟲)

因為有人預約, 需還下面兩本書 : 
  1. Python網路文字探勘入門到上手
  2. Python網路爬蟲 : 大數據擷取、清洗、儲存與分析 : 王者歸來
這兩本書都不錯, 但我覺得 No.1 這本陳寬裕寫的更佳, 篇幅不大, 但行文流暢且編排較順, No,2 則蒐羅較廣, 都值得一看.