2015/04/17

新書發表:Python程式設計入門

嗨,各位好,我寫了一本Python程式語言的入門書,還請多多指教。

書名:Python程式設計入門
作者:我
出版社:博碩文化(書號PG21421
出版日期:2015年3月29日
頁數:544頁
ISBN:978-986-434-005-7


自從1980年代末以來,Python程式語言經過二十多年的耕耘,蓬勃發展,已成為世界當紅的主流開發語言之一,不論是開放原始碼界和商業公司,採用Python來開發軟體專案的例子比比皆是,更有許多成功的案例,而且不論是哪個領域,諸如網站開發、機器人控制、影像辨識、數學運算等,都可見其身影,足可知Python擁有非比尋常的的彈性與能力。

回想起過去的年代,當作者還是莘莘學子之時,學校開設的課程大都為C/C++/Java等語言,而在更早之前,作者的老師們學習的第一支程式語言,多半是Basic或Pascal,時至今日,許多學校與程式設計課程紛紛轉而使用Python作為教學語言,不僅是因為其語法簡潔易懂,適合作為初學者走入程式世界的第一支語言,也因為Python擁有豐富的功能與特色,即便將來欲走向其他領域或某特定的範疇,也都能使用Python,不必耗費時間學習另一支語言。

這是一本Python程式語言的入門書,將會詳細介紹Python的語法和語意,此部份屬於基本功夫,就好像學習英語時,需要學習單字、詞性、時態、文法等等,然後加以運用依據適當的結構寫出句子,也就是撰寫程式;但光如此並不足夠,我們還會介紹程式設計的各種概念,諸如函式、遞迴、閉包、物件等,並且會解說程式執行的抽象模型,透過該模型,思考寫下來的程式碼(死的)在執行時的動態交互作用(活的),如此一來,方能通透明白。

Python目前分為2.x版與3.x版兩個系列,彼此並非完全相容,換句話說,某些2.x版舊程式無法給3.x版執行,反之亦然;雖說3.x版較新較先進,但仍有很多人使用2.x版,仍有眾多的2.x版舊程式,所以本書將會同時兼顧,當有差異處時將會特別註明。以2.7、3.3、3.4版為主,這也是目前最新最普及的版本。

學習資訊科學相關知識時,免不了還是需要英文,因為許多第一手資料與文件,都只有英文,所以本書會放入英文詞彙,為了避免搞混,將會多次出現中英並呈,若原詞彙沒有適當且眾人公認的適當譯詞,或是直接使用原詞彙更顯得清楚明瞭時,將會直接使用原來的英文詞彙。


章節大綱:

第1章,介紹電腦的硬體與軟體,說明程式語言在其中扮演何種角色,簡述Python歷代版本的演進差異,然後下載並安裝Python實作,準備好開發環境,然後才能開始學習Python語言。

第2章,真正開始動手撰寫Python程式,從基本概念開始講起,介紹Python直譯器、何謂名稱與物件、指派的意義、各種運算式與述句、基本的資料型別、迴圈、函式、程式庫模組等。企圖以一章的篇幅介紹必備的基礎知識,有了這一章為底,接下來就可深入,繼續探究Python的各個面向。

第3章,介紹數值型別,包括整數、浮點數、複數、布林等等,以及可用的運算,介紹與數值相關的概念,譬如加總、平均、兩數的關係等等,數值是程式設計的基礎,說到底,電腦裡的所有資料終歸是一堆0與1、也就是數值罷了。之後會介紹具備特殊用處的十進位數和分數,以及整數的位元運算。

第4章,介紹Python的主力容器型別:序列,以及相關的迭代概念,這是一種一個接著一個、具備順序性的資料結構與操作方式,Python最重要的序列型別:序列,便是其中之一,許多議題與功能皆圍繞著串列打轉,譬如可迭代者與迭代器。除了串列外,還有元組與字串,並且介紹字串格式化。

第5章,介紹序列型別以外最重要的容器型別:字典,這是一種具備映射形式的的資料結構,每個元素都是所謂的鍵值配對,本章會介紹雜湊、集合等相關主題,最後簡介具有預設值和具有順序性的特殊字典。

第6章,函式是一門大學問,此章將從基礎逐步介紹函式的裡裡外外,包括如何定義、參數的傳遞、回傳值等基本知識,以及可被呼叫者和可視範圍等概念,並會以詳細的圖示解說何謂命名空間與環境,藉以了解遞迴、閉包、高階函式等進階主題,最後介紹直接由Python語法支援的產生器和延遲綁定的概念。

第7章,本章主題包括資料與編碼,重點放在檔案開啟與文字檔案,將會詳細解釋何謂文字編碼系統,期望能掃除此方面的疑惑,搞清楚底端的二進位資料只不過是一堆0與1(或說是一堆數字),須經過解釋後才具備意義;檔案是種抽象概念,不一定指硬碟裡的檔案,也可代表網路連線或其他資料讀寫來源。

第8章,認識的工具越多,便能根據狀況使用最適當的工具來解決問題,此章介紹串列和字典以外的其他容器型別,包括具名元組、雙向佇列、模組heapq、ChainMap、陣列等等。

第9章,函式實在太過重要,所以此章再談函式,再次深入探討,主題包括遞迴、尾呼叫、裝飾器、函數式程式設計、延遲執行等。

第10章,模組是管理大型軟體的重要利器,Python程式就是由模組所組成,本章介紹如何模組與套件的概念,如何建立模組,以及各種匯入模組並使用的語法,如何尋找、安裝、使用別人開發好的模組。

第11章,雖然至此章才介紹物件導向程式設計,但我們早已使用各種型別的物件撰寫程式;本章將介紹物件導向的主題,包括定義類別、封裝、繼承、重載、覆寫與多型,也會介紹多重繼承與後設類別。

第12章,現代程式語言碰見錯誤、無法執行、意外狀況時,都會提供一套異常處理機制來讓我們著手處理,本章介紹如何引發異常、該怎麼捕抓異常、如何自行定義異常類別。

第13章,學海無涯,此章將列出讀完本書後,接下來可進一步探索研究的主題。

附錄A,分類列出Python內建函式。

附錄B,概略列出Python 2.x與3.x版的差異。

附錄C,ASCII字元表,簡述ASCII和其在今時今日的用處,以及與其他文字編碼系統的關係。

附錄D,參考資料與學習資源,包括Python官方網站裡最常使用的頁面,學習Python的書籍著作與線上教材網站,程式設計的論壇、Q&A討論區、社群以及擷取最新資訊的新聞網站,並列出幾個不錯的練習題網站,藉以磨練寫程式與思考的能力。


原始碼:

書裡所有的原始程式碼檔案,都已上傳至GitHub,可自由下載,直接點按網頁上的「Download ZIP」,便會將所有檔案打包成zip壓縮檔下載,或自行使用git指令下載;章節內文中都會註明相對應的檔名,此外,某些練習題需要的輔助檔案,也一併放在此處。


勘誤表:

若發現任何錯誤,還請留言告知,謝謝。

日期:2015.04.13
嚴重程度:中
位置:第27頁,第1行。
原文:因為Python有可變物件(int、float、str)與不可變物件(list)之分
修正:因為Python有不可變物件(int、float、str)與可變物件(list)之分

日期:2015.04.16
嚴重程度:中
位置:第26頁,下方程式碼區塊的第2行、第3行、第6行、最後一行。
原文:>>> weight = 177          # 身高
修正:>>> height = 177          # 身高

原文:>>> height = 68           # 體重
修正:>>> weight = 68           # 體重

原文:>>> li4 = [name, weight, height, title, langs]
修正:>>> li4 = [name, height, weight, title, langs]

原文:['Frank', 185, 75, 'engineer', ['C', 'Python']]
修正:['Frank', 177, 75, 'engineer', ['C', 'Python']]

日期:2015.04.19
嚴重程度:小
位置:第24頁,小節「return述句」的第一段。
原文:但可回傳tuple物件,所以可把許多物件放在tuple裡,
修正:但可回傳容器物件,所以可把許多物件放在tuple或list裡,

底下是一般性質的文句修改,嚴重程度都是「小」:

日期:2015.04.16

位置:第71頁,2.7節中的第2小節標題。把全形冒號改成半形冒號。
原文:忘記冒號「
修正:忘記冒號「:

日期:2015.04.19

位置:第49頁,小節「break述句與continue述句」的第1行。
原文:使用迴圈述句(包括while與for)時,有時處理到某輪時,
修正:迴圈述句(包括while與for)處理到某輪

日期:2015.04.19
位置:第52頁,下方程式碼區塊的上面那一段。
原文:函式定義完成後,便可使用它,
修正:函式定義完成後,

原文:函式呼叫屬於運算式
修正:函式呼叫在Python語法中屬於運算式

原文:在括號內傳入適當的參數
修正:在括號內傳入適當的參數

2015/04/16

LinkIt ONE小發現:類別的方法無實作,造成整支程式無法執行

一支很簡單的程式,如果沒有紅色的部份,就可以正常執行,LED會閃爍,序列埠可接收訊息。

#define BAUDRATE 115200
#define LED_PIN 13

class MyClass
{
public:
  void aMethod();
};

void aFunction(){
  MyClass x;
  x.aMethod();
}


void setup(){
  Serial.begin(BAUDRATE);
  pinMode(LED_PIN, OUTPUT);
}

void loop(){
  digitalWrite(LED_PIN, HIGH);
  Serial.println("LED high");
  delay(300);
  digitalWrite(LED_PIN, LOW);
  Serial.println("LED low");
  delay(300);
}


但若有紅色的部份,建置時會出現警告訊息「warning: undefined reference」,但編譯、連結仍成功,也可以燒錄到板子裡,但卻無法執行,LED不會動,序列埠什麼東西也沒有。

2015/04/14

LinkIt ONE:向NTP伺服器取得時間

透過WiFi,傳送UDP封包,向NTP伺服器取得時間。

底下程式碼中,關於NTP的部份,主要來自Arduino官網的範例Arduino - UdpNTPClient,改用LinkIt ONE的API。原始碼可到GitHub下載。

先看看執行結果,送序列埠送出控制字元,操控WiFi模組,連線後,送出UDP封包向NTP伺服器取得時間。有哪些控制字元,請見程式碼。


程式碼:

#include <stdarg.h>
#include <LWiFi.h>
#include <LWiFiUdp.h>

#define BAUDRATE 115200

// 請修改成你的WiFi名稱與密碼
#define WIFI_AP "__WiFi_AP_NAME__"
#define WIFI_PASSWD __WiFi_PASSWORD__"

// NTP伺服器
#define NTP_SERVER "time.stdtime.gov.tw" // (118, 163, 81, 61)
IPAddress ntpServerIP;
#define ntpServerPort 123

#define LOCAL_PORT 2390

#define NTP_PACKET_SIZE 48

// UDP客戶端
LWiFiUDP udpcli;

// 類似於printf
void pf(const char *fmt, ... ){
    char tmp[128]; // limited to 128 chars
    va_list args;
    va_start(args, fmt);
    vsnprintf(tmp, 128, fmt, args);
    va_end(args);
    Serial.print(tmp);
}


// 根據WiFi模組狀態,回傳容易看懂的字串
const char * const wifi_status_str(LWifiStatus ws){
  switch(ws){
    case LWIFI_STATUS_DISABLED:
      return "WiFi module status: disabled";
    break;
    case LWIFI_STATUS_DISCONNECTED:
      return "WiFi module status: disconnected";
    break;
    case LWIFI_STATUS_CONNECTED:
      return "WiFi module status: connected";
    break;
  }
  return "WiFi status: error, no such status";
}

void setup(){
  Serial.begin(BAUDRATE);
 
  udpcli.begin(LOCAL_PORT); // 綁定某的通訊埠
}


// 送出詢問時間的UDP封包,詳情請看RFC 5905 unsigned long sendNTPpacket(IPAddress &address)
{
  byte buf[NTP_PACKET_SIZE];
  memset(buf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
  // Initialize values needed to form NTP request
  buf[0] = 0b11100011;   // LI, Version, Mode
  buf[1] = 0;     // Stratum, or type of clock
  buf[2] = 6;     // Polling Interval
  buf[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  buf[12]  = 49;
  buf[13]  = 0x4E;
  buf[14]  = 49;
  buf[15]  = 52;

  // send a packet requesting a timestamp:
  udpcli.beginPacket(address, ntpServerPort);
  udpcli.write(buf, NTP_PACKET_SIZE);
  udpcli.endPacket();
}


// 拿到秒數(從1900年1月1日 0時0分0秒),印出
// 轉成Unix epoch time,印出
// 捨棄年月日,印出時分秒,HH:MM:SS void printTime(unsigned long secsSince1900){
  pf("Seconds since Jan 1 1900: %u\n", secsSince1900);
 
  unsigned long epoch = secsSince1900 - 2208988800UL; // seventy years
  pf("Unix time: %u\n", epoch); // 00:00:00 1 January 1970, UTC
 
  epoch %= 86400L;
  unsigned long hh = epoch / 3600;
  epoch %= 3600;
  unsigned mm = epoch / 60;
  epoch %= 60;
  unsigned ss = epoch;
  pf("The UTC time: %02u:%02u:%02u\n", hh, mm, ss);
}


// 主迴圈,從序列埠接收控制字元,做該做的事情 void loop(){
  if(Serial.available()){
    char d = Serial.read();
    switch(d){
      case 'l':{ // 搜尋WiFi網路
        int i;
        int n_ap = LWiFi.scanNetworks();
        pf("Number of WiFi AP found: %d\n", n_ap);
        for(i = 0; i < n_ap; i++){
            pf("%d, %s, RSSI: %d\n", i, LWiFi.SSID(i), LWiFi.RSSI(i));
        }
      }
      break;
      case 's':{ // 印出狀態
        LWifiStatus ws = LWiFi.status();
        Serial.println(wifi_status_str(ws));
       
        uint8_t ma[VM_WLAN_WNDRV_MAC_ADDRESS_LEN] = {0};
        LWiFi.macAddress(ma);
        Serial.print("MAC address: ");
        int i;
        for(i = 0; i < VM_WLAN_WNDRV_MAC_ADDRESS_LEN-1; i++){
            pf("%02X:", ma[i]);
        }
        pf("%02X\n", ma[i]);
       
        if(ws == LWIFI_STATUS_CONNECTED){
          IPAddress ipa = LWiFi.localIP();
          Serial.print("localIP: ");
          ipa.printTo(Serial);
          Serial.println("");
         
          Serial.print("subnetMask: ");
          ipa = LWiFi.subnetMask();
          ipa.printTo(Serial);
          Serial.println("");
         
          Serial.print("gatewayIP: ");
          ipa = LWiFi.gatewayIP();
          ipa.printTo(Serial);
          Serial.println("");
        }
      }
      break;
      case 'b':{ // 開啟WiFi模組
        Serial.println("LWiFi.begin() turn on WiFi module");
        LWiFi.begin();
      }
      break;
      case 'e':{ // 關閉WiFi模組
        Serial.println("LWiFi.end() turn off WiFi module");
        LWiFi.end();
      }
      break;
      case 'c':{ // 連接WiFi AP
        Serial.print("Connecting...");
        if(LWiFi.connect(WIFI_AP, LWiFiLoginInfo(LWIFI_WPA, WIFI_PASSWD)) > 0){
          Serial.println(" succeed");
        }
        else{
          Serial.println(" failed");
        }
      }
      break;
      case 'd':{ // 切斷與WiFi AP的連線
        LWiFi.disconnect();
        Serial.println("Disconnected");
      }
      break;
      case 't':{ // 向NTP伺服器詢問時間
        if(ntpServerIP == INADDR_NONE){
          if(LWiFi.hostByName(NTP_SERVER, ntpServerIP) != 1){
            pf("DNS lookup failed: %s", NTP_SERVER);
          }
        }
        if(ntpServerIP != INADDR_NONE){
          pf("Send request to NTP server %s\n", NTP_SERVER);
          sendNTPpacket(ntpServerIP);
        }
      }
      break;
    }
  }
 
  if(udpcli.parsePacket()){ // 收到NTP伺服器傳回來的時間
    byte packetBuffer[NTP_PACKET_SIZE];
    udpcli.read(packetBuffer, NTP_PACKET_SIZE);
    const unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    const unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    const unsigned long secsSince1900 = highWord << 16 | lowWord;
    printTime(secsSince1900);
  }
}