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 強大許多。

3 則留言:

  1. 這些在c#, vb, ruby 都有, 老實說也沒啥特別的.

    回覆刪除
    回覆
    1. 針對樓上的回覆,我想...很多語言都有其相似之處吧~
      身為一個python初學者,我覺得這篇文章相當受用! 謝謝你:)

      刪除
  2. 簡明又實用的平行處理技巧, 讚!

    回覆刪除