2012年8月5日 星期日

從需求出發理解背後技術的思考脈胳

最近在技術上多了不少體悟,關鍵是要掌握住問題的需求,接著不斷思考和嘗試,避免卡在特定的規則或工具裡,將重心放在隱藏在這些東西背後的核心思想。

直接看現有的工具或別人的作法很難有所體會,但自己從頭邊做邊想,會得出自己一套理論, 接著會出乎意料地更快理解這些工具和作法。畢竟在足夠嚴苛的需求下,方法可能有些差異,背後的精神卻是類似的。

比方說,若程式反應速度要求在 1ms 以內,全部資料來源都得在記憶體或網路,不能使用硬碟。那麼,針對這種應用,單機程式能做的事只有將東西塞在記憶體裡取用

然而記憶體有限,資料總有放不進去的時候。在這種速度要求且資料過大的前提下,可能的作法大概是:

  • 像 cache 那般有套機制留住最常用的資料。
  • 在記憶體裡存 index,index 內存必要的資料,若需要更多資料再從硬碟讀。
  • 先做前處理「壓縮」資料成摘要,摘要必須小到可以放入記憶體內。資料的品質(正確性)也許會有一些損失,但是是必要的取捨。之前在讀 YouTube 關聯影片和 IBM 的 Watson 論文時,有看到類似的思路,工作上也做過類似的事。和前述方法不同的地方是,資料的筆數變少,轉為另一種高質量的資料。

再以 Test Driven Development 為例,最重要的不是 TDD 的三步規則,也不是相關工具要怎麼用,而是導入「在設計的開頭, 就將測試視為主要考慮項目」,其它東西都是這個想法的衍生。

從這個角度出發,實作久了自然會理解為什麼介面要開洞放入物件,為什麼需要 factory 隔離生成和操作邏輯。至於是否真的有先寫測試,現在我覺得不是鐵則,在有為測試而考慮的設計下,在必要時補測試可能更划算,將時間花在刀口上。但若一開始沒為測試考慮,事後要補就很辛苦,加測試的價值又更低了

再往上拉一個層次來看,解決問題的前提,本來就是如何確認問題有被解決,若無法確認問題有無被解決,用什麼方法也是白搭,不知成效如何。從這個角度來看,在設計之初就考慮測試,並不是什麼新穎或強人所難的事。但要走到這步,如同學其它東西一般,需要累積不少經驗

總結來說,要學習一個技術最好的方法,就是在有適當的需求時再學。平時看到和自己需求無關的知識,加減有個印象在腦裡即可。待要用到時可利用腦裡的索引找出相關資訊,先有個廣泛認知。接著根據自己的需求可以明白那些是比較相關的知識,邊看邊動手做些雛型試試。有了這些操作經驗和背景知識後,再從頭確認一次自己真正的需求為何、有那些限制和對應解法,就能理解要如何應用這些技術和工具到自己的情況裡。

2012年5月6日 星期日

The Linux Programming Interface 讀書心得

書還沒出以前就聽 Scott 推薦過 TLPI,直到因為工作需要,才買回來看。斷斷續續大概看了三個月半,來寫一下心得。先說結論,若需要寫 Linux programming,這本是極佳的選擇,它是我買過最貴最厚的書,同時也是我買過最划算的書。

全書共 64 個章節近 1500 頁,在 2010 年底出版,算是相當新且完整的書。我看的方式是:每天沒事翻個四頁,有事時針對需要的重點看,不見得每個章節都要看完,有解決當下需求即可。持續這樣運作個一陣子,效果相當好。從上面的照片可看到我插的一堆書籤,發票、鐵尺、名片、計算紙等,不知不覺就放了 14 個,看了小有成就感。

本書最大的好處是,作者對每個主題循序漸進地提供鉅細靡遺的說明,不只是 POSIX 或 Linux 能做什麼,同時也會提供必要的基礎知識,像在 network programming 的章節,會補充說明 TCP/IP 的運作方式,而且相當容易吸收。ch 41、42 則是說明 static 和 shared libraries,也是相當實用的知識。之前曾在網路上看過一些片段知識和一些動手操作的經驗,但一直沒有拼出全貌。直到看了這兩章後,才有種打通的感覺,更有把握知道底層是怎麼運作的,從而解決工作上的疑問。

所以,只要有耐心啃完書中相關內容,就有個八成把握知道怎麼做才對。相較於在網路上搜尋,更能系統地了解 Linux 能與不能做什麼事,有利於判斷可行的方案。偶而我也會看 man page 的說明,交叉對照效果更好。作者說的是他整理消化後各系統通用的知識,而 man page 可進一步反映出自己使用 OS 版本的細節。

本書另一大好處是,每個章節都有附完整的程式碼,輔以執行結果,來說明系統的一些特性。像是「20.12 Signals Are Not Queued」,為了說明 signal 只有保證送出後至少會收到一次,但不是送幾次就收到幾次,作者寫了個小程式 A 送個上一百萬次 signal 給 B,B 則是在 signal handler 裡計數,結果在作者的這次實驗裡, B 只收到 52 次。我很喜歡這種實證的方式,現在的系統太複雜,在不同平台會有不同狀況。了解知識的概念後,手邊還是要有程式實測,感覺才會踏實。

除了內容完善以外,對書中有疑問時,寄信給作者也會得到很友善的回應。舉了這麼多優點,實在是讓人想不出不買它的理由,也難怪 Amazon 上 35 個評價全都是五顆星了

2012年2月19日 星期日

專注於滿足需求而非工具或方法

看到 command 提到《Don't Fall in Love With Your Technology》,而有一些感觸。

從高中開始,我一直想弄明白 Perl、Python 到底那一個比較好用,這樣我學其中一個就可以了。後來又多了 Ruby 這個選項,讓這問題變得更複雜。大概到大學後期或研究所的時候,我才肯定這是一種信仰上的爭辯,而將這個問題拋之於腦後。

同一時期,我也花了滿長一段時間才明白許多問題沒有標準答案得視情況而定。每當對此有所體會時,就會想起大學電子學老師整年重覆強調的一句話:「沒有前提,就沒有答案」。雖然兩學期的電子學都是低空飛過,這句話深深印在心裡,只是那時我對這句話的理解仍不深,還需時時重新琢磨它的含意。

我花了更長的時間才將前面兩個心得連結在一起,從而明白任何工具或方法的爭辯很可能都是偽命題,重點在於需求是什麼?要如何滿足需求?如今回想起來,《不要自称为程序员》將這個觀念解析得相當清楚,相當值得一看。

舉例來說,「vim vs. emacs」是個偽命題,這取決於自己當下的環境為何。若團隊內多數人使用 emacs 且自己兩者都不熟,那麼 emacs 是較為合理的選擇。反之,若自己相當熟 vim 而團隊內多數人兩者都不熟,那繼續使用 vim 較為合理。重點在於「如何有效率地在自己的環境下解決問題」,而非「一般而言,那一個編輯器比較強?」

再以軟體開發的方法來看,「agile vs. 某個軟體開發方法」也是偽命題,不論 agile 公認的定義為何,重點在於滿足需求,而滿足需求不見得需要一套完備的軟體開發方法;有完備的軟體開發方法不見得能滿足需求。要滿足需求有太多事要做,研讀相關技術、軟體開發、市場行銷等,軟體開發可能是滿足需求的其中一項基石,但不是全部。若滿足需求的前提需要改善軟體需求,自然需要改善它;反之則否。《Joel on Software - 別讓架構太空人嚇到你》對「開發軟體的方法 vs. 滿足需求」提了生動的描述。

舉另一個具體例子,「是否需要重構?」往往帶來許多爭議性的討論,各方人馬(PM、RD、QA、...) 對此有不同看法。若這段程式一直都不需要加新功能,那的確不需要重構。重構只會花費時間讓程式碼變漂亮,對於滿足需求沒有任何影響。反之,之後需要繼續大幅加功能,逐步重構部份功能,則對完成產品(滿足需求)大有幫助。

最近幾年有一個很紅的議題,開發網站是用 Ruby on Rails 好,還是用 ... 好。最近一年可能還會多一些人問是否要改用基於 node.js 的新 framework。要回答這問題得先看需求為何,若只是做幾頁的小網站,用什麼方法差異都不大;若是做長期維護的大網站,要看目前團隊成員熟悉的工具和程式語言為何,再來評估使用 Rails 的相對成本。若再涉及和後端整合,又和既有的 code base 有大幅關聯。而要回答這一切一切衍生的議題,還是得先看:究竟需求為何,基於什麼原因而採用 X 會更好?以 Justin.tv 為例,《Django: Why is Justin.tv porting their codebase to Django from RoR?》說明 Justin.tv 轉換的主因是全部程式都是用 Python 寫的,此外,他們也想藉機重新設計一遍架構,去除 legacy codes,以符合現今的使用需求。

舉這些例子的用意不是無限上綱地說工具和方法都不重要,而是強調將焦點放在如何滿足需求,若有需要選用好工具,才有必要討論它。問錯問題的話,永遠不會得到有用的答案。

2012年1月19日 星期四

在 Linux 下開發 C/C++ 的新手指南

2018-08 更新

依近幾年的經驗更新了另一份介紹,見這裡



新加入一個專案,最先面對的課題是如何正確地編譯和執行專案,可從 "It works on my machine" 如此地風行,印證這件事的困難性;再來則是閱讀負責工作相關的程式碼。至於發揮程式語言的特性,運用高階設計模式等,都是另開新專案或熟悉狀況後才有機會發揮。

過去數年沉浸在愉快的 scripting language 和開發新專案中,一直沒踏入這殘酷的世界。這篇記錄在這樣的情境下,可能需要的技能,結算一下這一個多月的心得,全都是血淚談啊 ...。

系統工具

熟悉作業系統的安裝套件是首要之務,這樣才知道如何補足需要的 header、library,或是安裝含 debug symbol 版的函式庫以執行 gdb 觀察程式或除錯。參見《自行編譯含 debug symbol 的套件 (package)》了解 Ubuntu/Debian 下的套件命名規則。

在未安裝套件的情況下,可用

  • aptitude search SUBSTRING # 找套件
  • aptitude show PACKAGE # 顯示套件用途
  • apt-file search X # 找出 X 包在那個套件裡,找 header 時很有用。

注意在用 apt-file 前要先跑 sudo apt-file update,不然搜不出東西來。

對於已安裝套件,可用

  • dpkg --search SUBSTRING # 找出安裝在那個套件,已知 header 時,適合用來找 library
  • dpkg -L PACKAGE # 列出套件內容,可用來找 header、library
  • locate SUBSTRING # 我比較常用它找 header 的位置,再觀看 header 內容

執行 locate 前記得先執行 sudo updatedb,原因同 apt-file。

《除錯技巧:在 Ubuntu 上找出第三方函式庫的程式碼》用一個小例子說明如何使用這些工具找出原始碼協助除錯。

其它參考資料: How To Manage Packages Using apt-get, apt-cache, apt-file and dpkg Commands ( With 13 Practical Examples )

編譯

連結

這一塊讓我卡了一陣子。一些粗淺心得:

執行

光只是讀程式碼就像大海撈針一樣,不太有效率。可從動態執行過程找出主要執行的路徑,再專注相關的程式碼。

1. strace 和 ltrace

srace 是分析執行行為的強大工具,google 一下會看到很多別人的個案心得,看看再自己試一試,很快能上手,不知能發揮它多少功能。這裡列自己用的兩個小案例:

反而是 ltrace 一直都想不到使用它的時機,也沒找到好的個案心得文。

2. gdb

gdb 的重要性不需多說明,之前的幾則心得:

強烈建議使用 cgdb,簡易安裝 + 無痛上手,瞬間省下大量操作和讀碼的時間。

3. 打開除錯功能

依照開發者的習性,一定會留後門讓自己方便除錯,從這角度下手也可省下不少時間:

4. 載入函式庫

除以上所言外,我另外有找過畫出程式流程的靜態和動態分析工具,像是畫 call graph 或是 C 的 cflow。不過 C++ 的靜態分析效果很糟,就沒花太多時間研究。目前用 strace 和 gdb 覺得已夠用了,不知用工具產生 call graph、class 相依圖或其它東西,是否會更有幫助。待有需求看整體的程式時再來試試。

閱讀程式碼

聽了大家的建議後,做了一些實際操作,而有些心得:

Eclipse CDT 雖然方便,後來我還是用 gj 居多。原因有幾點:

  • 我已很習慣用 vim + screen 做事,gj 最合這個情境
  • id-utils 真的是超級快
  • 我針對自己的需求更新 gj 多次,愈用愈順手

另外 ack 也滿方便的,懶得建 index 或是想比對子字串時,可直接使用。當然 id-utils 也支援子字串比對,只是暫時懶得為此修改 gj 的程式,目前大部份需求是找完整的 symbol。

熟悉 Linux 系統程式

在基本工具都上手後,打算每天抽一點時間加減讀一點相關知識。一兩年下來應該會有不錯的成果。目前打算讀《The Linux Programming Interface》,年假時試看看效果如何。

這一個月的心得以了解 /proc 為主,對觀察 CPU 用量、RAM 用量、載入那些函式庫、multi-thread、程式執行狀態等都很有幫助:

結論

即使大概知道有那些東西,還是需要實際動手的經驗,才會真的學進去。一個月下來進步了不少,不過對於要面對的戰役,還有一大段路要趕上,還有很多很多要學的。

2012-01-29 更新

補上一些後來新寫的連結。此外,《The Linux Programming Interface》 相當實用,讀 ch1 ~ 3 讓我補足不少基礎知識。ch41、42 講解 shared library 也相當值得一看。相關心得見《The Linux Programming Interface 讀書心得》

2013-07-13 更新

備忘效能分析相關的工具:

出處:Linux Performance Analysis and Tools

2013-07-20 更新

將後半部份內容抽出來,另寫了一篇比較完整的文章:《了解 C/C++ 程式行為的技巧》。

2011年11月23日 星期三

使用 Django 的雜感

換工作後大概很少會用到 Django, 在這對它做個了結吧。

先說結論

對於不熟 web 又想用 python 開發的人來說, Django 仍是首選, 主要的優勢有:

  1. 官方文件超級豐富, 網路上文件也豐富, 也有出一些書, 不過書上內容應該沒官網新, 看官網就夠了。不夠的話再看原始碼也比較方便。
  2. 社群龐大, 有許多 middleware 和 plugin 可用, 像多人合寫 web 一定會需要 database integration, South 相當好用。其它像用 Facebook / Google / Yahoo 等帳號登入, 也都有整合好的套件可用。
  3. 框架本身實作了許多 web 相關功能 (像 session、cookie、cache、傳輸 zip 後的內容), 可避免犯錯 (像是有擋 CSRF), 很多東西是新手從來沒想過的, 但框架都有考慮到並且實作好了。在使用的過程中可學到這些知識。
  4. 極佳的向下相容。官方的 roadmap 會明確提到那些功能已 deprecated, 並會在未來的那一版移掉, 有滿長的過渡期。使用者升級的負擔相當小。
  5. 提供一組規範, 切開 model、view、control, 還有 class / table 命名規則等, 減少團隊合作的問題。

當初選 Django 的主因是第一、二點, 第三點稍微有想到, 而第四、五點是使用後得到的驚喜。

說完結論後, 要開始碎碎念我的不滿, 請各位看倌記得, 即使如此, 我還是推薦不熟 web 又想用 python 開發的人使用 Django, 理由如前所述。

關於 template

Django 內建的 template 不好用, 效率也差, 甚至在 FAQ 裡有一項提到「I can't stand your template language. Do I have to use it?」。有些人可能覺得 template 的效率不是重點, 瓶頸會在 database。當我費盡心力減少 SQL、改 schema、改index、改寫 SQL 將 database 花的時間壓到極致後, 卻發現 template 怎麼縮都要 0.1s, 讓我很無力。更別提在關掉 i18n / l10n 前, template 要 0.2s。看著簡單的 template 內容, 很難理解為什麼這樣的東西要花到 0.1s。

此外, template 的語法很受限, 不過到 1.3 版後 include 多了 with 的語法方便代入子版面後, 變得好用許多, 不然有類似這種簡單需求時要另寫 templatetag, 實在是多餘又不易懂。相關心得見之前寫的文章。實在是換掉也為難, 不換也為難。

關於 ORM

先來個免責聲明, 若是需要頻繁地寫多個不同的小型網站, 用 ORM 是利多於弊, 可減少重覆的程式碼。以下的論點基於我的個人經驗, 需求是長期維護一個資料量大且有嚴苛速度需求的網站。

之前已寫過幾篇 ORM 心得, 在《Django 和 Python 操作 database 時的額外負擔》提到實測大量數據的情況有多慢, 注意我的使用情境有超過百萬筆資料, 若資料量沒那麼大, 這個負擔較無所謂。

《撰寫資料庫相關程式的心得》有提到使用 ORM 的成本。這裡要補充的是, 無論如何, 我們都需要分開「資料庫的操作」和「邏輯操作」。而在程式裡直接使用 ORM 並沒有隔離好兩者。

舉例來說, 在頁面裡使用 get_user("fcamel") 會比 User.objects.get(name="fcamel") 來得好。想想要如何針對這個頁面寫 unit test, 會發覺後者仍和 database 有很大的相依性, 不容易 mock。或著換個說法, get_user() 的抽象程度比直接使用 ORM 高。get_user() 裡是使用 ORM 還是下 raw SQL, 都和使用者無關。當需要連續幾個 ORM 操作以達成一個目的時, 另外包函式的優勢會更明顯。

若能接受上面的論點的話, 會發覺 ORM 的優勢又少了一點。所以, 我個人的看法是: 重點是必須另外包一層 API 存取資料, 供邏輯操作使用。所以, 對前端開發者來說, 是否使用 ORM, 影響不大。但對後端開發者來說, ORM 的缺點遠大於優點。ORM 最吸引人的地方是提供不錯介面隔離 database, 可以有彈性地存取資料並且不用太了解如何寫 SQL。但隨著使用經驗漸增, 會發覺這些優點並非事實。但若一直不去了解 database, 不會發覺付出的隱性成本。

題外話, 聽說 SQLAlchemy 相當強大, 不知實際用起來效果如何。我後來淡化使用 ORM 的場合, 加上需要使用 South (基於 Django ORM 的 plugin), 不方便換掉, 就沒有研究 SQLAlchemy 了。

關於 test

內建的 django.test 相關模組不太好用, 而且執行 test 要先重建全部 table, 然後在每個 test case 前 truncate 全部 table, 效率不好。若能指定只 truncate 需要的 table, 可省下許多時間。通常 test case 是愈寫愈多, 實務上執行 test case 的效率相當重要。

使用 South 後更會有 production schema 和 test schema 不同的問題, 因為 production 用 South 建 schema, 中間可能用到客制化的 SQL 改 schema (如建 multiple column indexes), 但 django.test 不會呼叫 South, 而是用 Django 原本讀 models.py 建 schema 的方式。我後來改用自己寫的模組來建 test database, 沒研究後續發展, 不知後來是否有修正。

關於 coding style

Django 違反許多 PEP 8 的規則或 Python 精神, 像是:

  • 在程式碼裡面 import 別的 module (而非開頭), 並且有 circular import。
  • 有多種方法做一件事。我很討厭這點, 像自訂 login 的重導頁面卻沒成功, 除錯時很麻煩, 要搞清楚多種規則的執行順序, 才知道問題出在那。
  • 有許多 lazy initialization。我不確定這是否違反 Python 社群的 "explicit is better than implicit", 我個人不喜歡一堆 lazy initialization, 很難掌握程式的行為。

以上這些事對使用者有什麼影響? 當行為不合預期, 文件也看不出所以然時, 讀原始碼並加 log message 是滿有效率的除錯方法。上述都是我在研究功能 (像是如何使用 cache) 或除錯時, 讀原始碼遇到的困擾。

結語

除前面一再強調的「結論」外, 再多強調一下, 一個東西愈多人罵, 表示愈多人用, 出事也愈好處理。本篇不是建議別用 Django, 也不是反串推廣, 只是之前一年多的使用心得。

2011年9月18日 星期日

Problem Solving 的技巧 (3):因事制宜

不知不覺,這類文章可以寫到第三篇,感到頗意外的。前兩篇是:

昨天看到 TonyQ 在 Soft_Job 寫的文章:《Re: [閒聊] 你在開發程式時,是重視績效還是品質。覺得深有同感,摘錄幾段如下:

對我們來講很多其實可以是 nice to have 的東西, 都會被我們當成 must have。

這個判斷是經驗跟 domain 累積下來的,沒有公式,沒有法則, 做久了你就是會知道什麼架構之後會一直噴 exception ,

而且你還會知道等他出事時你一定沒辦法好好處理, 所以你要在這個當下把它處理掉。

...

觀察他們影響到哪些地方,你有沒有能力測試他們, 還有你的環境允不允許這件事情帶來的不穩定性。

一般來講,如果是我個人自己的專案,我非常不介意大改, 只要我後續還有時間可以處理這些出來的問題。

對公司或者客戶的專案,我會採取相對保守的態度。

這是對風險管理的策略問題。

在 Plurk 上貼了這篇文章後,Thinkerqrtt1 提出另一面看法,強調「早期發現,早期治療」的好處。於是決定借題寫一下自己的看法。

在討論這個議題前,我想先強調,為了方便聚焦討論,我們往往會先偋除一些條件,或是先依自己的假設開始論述。而有爭論的地方,往往卻是這些隱藏的前提。好比說「寫測試碼重不重要?」單單這樣一個命題,只能討論出很模糊的概念,不論支持與否,看起來都有些道理。但加入一些條件後,像是「這是長期開發並有多人參與的專案」或是「後天要交的雛型」,相信大家對此會有不同的答案,也會少一些分歧。相關的想法,可以參考《黑天鵝效應》這篇有摘要讀書心得,其中敘事謬誤和戲局謬誤啟發我這個觀點。

我的想法是,在達到必要需求的前提下,依個人以及團隊的能力,看看能提昇多少品質。問題在於,何謂 must have、何謂 nice to have,每個人的見解不同。爭議點在於,每個人的能力不同,導致評估的實作成本不同。一樣是 nice to have,有些被認同可以做,有些則否。

( ps. 這篇文章不討論規格不明確的問題,這是另一個大議題。 )

舉例來說,A 認為現在重構只能提升一點品質,卻要花兩天;B 認同 A 評估的品質,但 B 覺得只要花半天。所以 A 覺得不划算,而 B 持相反意見。從這樣的評估結果很難說 A 或 B 誰對誰錯,也許 A 沒重構經驗不信任重構,也可能 A 有豐富的經驗,估得比 B 準。

在體認到個人能力不同、習慣不同的前提下,對自己的要求是盡量提高自己的能力,多付出時間提高品質,減少日後維護成本並能練功,形成正向循環對其他人來說,看對方是那種人,是「過」或「不及」,再從另一個角度和對方討論。

舉例來說,若團隊成員不熟或不認同 unit test。當下就要出貨了,這時硬要大家學習並實作測試,並不適合 (不如當「負面教材」,待下次專案的開頭提出討論)。但若成員有過相關經驗,同樣時程下,就能針對核心程式補些測試,花最小成本減少最多風險。當成員嘗到測試甜頭,不小心寫過多測試碼時,和對方討論優先順序,減少寫 C/P 值低的測試碼。

回頭看品質的問題,能力愈強、經驗愈豐富,增加 nice to have 的成本愈低,自然能前期處理,減少技術債。反之,在時程的考量下 (ship or die),能力和經驗不夠時,得選擇先放過一些潛在問題,日後仍有需求時,再花更多成本補救。先活下去,才有更多的本錢來還債。

因事制宜說來簡單,只有四個字而已。實務上卻需經年累月的經驗,不止要考量時程、未來需求變化等項目,也要留意每個人的能力和習性,才能在當下找到較為適當的平衡點。而增加經驗的方法,如同杜書伍在《打造將才基因》裡所言,除了比別人投入更多時間外,沒有其它的捷徑。

2011-09-25 更新

看到 Thinker 提了他的相關看法:《程式碼要清的多乾淨?》,裡面提了不錯的建議:

除了能力不同之外,個人的膽量、積極態度和價值觀也同樣影嚮著評估的差異。 我強調的是,積極態度和個人的能力呈正相關。
...
要評估自己的能力,並適度的承受犯錯的風險。
勇於嘗試、有控制性地犯錯,對於學習很有幫助。我覺得在我學習的前一大段生涯裡,過於謹慎而不敢犯錯,以致於學習速度較為緩慢。而我認識一些晚起步、卻成長相當快的朋友,都具有大膽嘗試、不斷犯錯的特質。他們快速地累積經驗,並培養出更多的膽識和更大的企圖心。

2011年8月7日 星期日

Python 的特別之處 (1)

從新手的眼中來看 Python,比較能看出 Python 和其它語言不同之處。最近有機會幫別人快速上手 Python,就順便整理一下我從中發覺 Python 較為突出的優點。

list、dictionary and string

平時 coding 最常用到的 container 就是 list 和 dictionary,另外也會常用到字串操作,Python 提供方便的方法來操作它們。string 可看成一個有實作 list 介面的類別,一些常用操作像是 slice:"abcd"[1:3] 回傳 "bc";負數的索引: "abcd"[-1] 回傳 "d";直接和 for-loop 整合在一起:

In [1]: for ch in "abcd":
  ....:         print ch
  ....:
a
b
c
d

讓存取這些常用資料型態輕鬆許多。

iterator

使用 iterator 比傳統的 for (i=0; i<n; i++) 來得清楚,Python 針對 iterator 下了不少工夫,提供好用的輔助函式,像是 enumerate 補足需要用到 index 的情況:

In [2]: for i, n in enumerate([1, 3, 5]):
  ....:     print i, n
  ....:
0 1
1 3
2 5

使用 zip 整合多個 list:

In [3]: names = ["John", "Marry", "Tom"]
In [4]: sexes = ["Male", "Female", "Male"]
In [5]: for name, sex in zip(names, sexes):
  ....:     print name, sex
  ....:
John Male
Marry Female
Tom Male

map, filter and reduce

任何使用過 map 的人,都會喜歡 map 輕巧的用法,來看幾個例子:

In [1]: map(int, ["12", "37", "999"])
Out[1]: [12, 37, 999]
In [2]: map(str, [12, 37, 999])
Out[2]: ['12', '37', '999']

int 是一個函式,將傳入的物件轉成整數;str 則是轉成字串。使用 map 可以將一個 iterator 轉為另一種 list。

另一個常見的情境是,從一個 list 裡篩選出需要的物件,比方說只留下偶數:

In [1]: numbers = [1, 2, 3, 4, 5]
In [2]: filter(lambda x: x % 2 == 0, numbers)
Out[2]: [2, 4]

或像 filter(lambda s: s.endswith('.py'), file_names) 只留下結尾為 ".py" 的字串。

除 map 和 filter 的重心放在轉換 list 之外,reduce 則是將 list 匯整成一個物件。有了這些函式,就能任意的操作 list,用以匯整或擴散資料容器。

比方說將一串數字加起來:

In [1]: numbers = [1, 2, 3, 4, 5]
In [2]: reduce(lambda x, y: x + y, numbers, 0)
Out[2]: 15

上面這個例子可以用內建的 sum 取代,來看另一個複雜點的例子,將一串 0、1 值合成一個整數:

In [1]: bits = [0, 1, 0, 0, 1]  # bits[i] 的值表示 2^i 的系數

In [2]: reduce(lambda x, (i, b): x | (b << i), enumerate(bits), 0)
Out[2]: 18

list comprehension

map 和 filter 雖然方便,要用到 lambda 或是混合使用時就沒那麼好讀了。Python 提供一個重量級的武器 list comprehension 來解決這問題。比方說留下偶數並乘以三再加一:

In [1]: numbers = [1, 2, 3, 4, 5]

In [2]: [n * 3 + 1 for n in numbers if n % 2 == 0]
Out[2]: [7, 13]

綜合以上的語法,可以輕鬆地寫出易懂的 quick sort

def qsort(numbers):
    if len(numbers) <= 1:
        return numbers                                                                                     
    pivot = numbers[0]
    rest = numbers[1:]
    smaller = [n for n in rest if n <= pivot]
    larger = [n for n in rest if n > pivot]
    return qsort(smaller) + [pivot] + qsort(larger)

對於習慣 C、C++、Java 世界的人來說,應該不曾看過這麼直覺易懂的 quick sort 吧。

tuple

tuple 是一個很妙的資料結構,它和 list 的主要差別是它是唯讀的,Python 裡鮮少有這種唯讀物件。不過它較易發覺的好處是被用在 Python 的 parallel assignment 和函式傳回值。

於是在 Python 裡可以這麼寫:

a, b = b, a # swap

Python 在看到 b, a 時會產生一個 tuple 表示 (b, a),再透過 tuple 達到 parallel assignment

函式也可以一次「傳回多個結果」:

In [1]: def divide_and_mode(a, b):
   ...:     if b == 0:
   ...:         return None, None
   ...:     return a / b, a % b
   ...:

In [2]: divide_and_mode(7, 3)
Out[2]: (2, 1)

In [3]: a, b = divide_and_mode(7, 3)

In [4]: a
Out[4]: 2

In [5]: b
Out[5]: 1

原理一樣是先轉成 tuple 再傳回,再視等號左側放什麼,決定要存成 tuple 或做 parallel assignment

2012-01-25 更新

應該沒什麼力氣更新續篇,在這裡簡短描述一下,有興趣的人可以找看看相關介紹。

with

在 Python 2.6 後,支援用 with 管理資源。像讀檔案可以用 with 的方式寫:

# 印出所有使用者的 id
with open('/etc/passwd') as fr:
    for line in fr:
        print line.split(':')[0]  

在進入 with 的 block 前,會呼叫 file object 的 __enter__ 以獲得 file descriptor;在離開 block 前會呼叫 __exit__ 關掉 file descriptor。即使中間呼叫了 sys.exit() 或丟出 exception,仍會執行到 __exit__,不用擔心會漏關。方便用在許多情境 (比方說 lock / unlock、自動 flush output buffer),易讀易用。

內建常用函式庫

除上述的基本資料結構和 string 外,還有 sqlitejson等。

簡單不易出錯的語法

舉幾個寫 C 可能發生的問題,但在 Python 的語法下則不會發生:

if (condition);
{
    // BUG!! 這裡的程式一定會被執行
}
if (x < 60)
    number_of_fail++;
    total_fail_score += x; // BUG!! 這行每次都會執行

另外,由於 Python 的 condition 只能是 expression,不能是 assignment。不會有 if (x -= 3 < 0) 這種 bug。

ipython

有 ipython 方便快速試語法、試函式庫還有開發程式。ipython 比簡單的 interactive interpreter 強大許多。