Python 2 Tutorial 第二堂(2)容器、流程、for 包含式





想想你平時撰寫的一些應用程式,大部份是在處理一組資料,Python 對管理資料用的容器(Container)型態,在語法上提供直接支援,加上 for 包含式(comprehension)的支援,在資料處理問題上可獲得不少的便利性。

Python 支援的容器型態有 listsetdicttuple 等。

list 型態

list 是有序且可變群集(Collection),在 Python 中,[1, 2, 3] 這樣的語法,即可建立含元素 1、2、3 而索引 0、1、2 的 list 實例。

list
與先前介紹過的 string 享有共同的操作。len 傳回 list 長度;in 可測試某元素是否在 list 中;+ 可以用來串接兩個 list* 可用來複製出指定數量的 list[] 可以指定索引,用以從 list 中取得元素,負索引是從最後一個元素計數,使用 [] 來切割 list 或許是最有用的功能。其他操作還有…
>>> [0] * 10
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
>>> ', '.join(['justin', 'caterpillar', 'openhome'])
'justin, caterpillar, openhome'
>>> list('justin')
['j', 'u', 's', 't', 'i', 'n']
>>>

set 型態

set 型態是無序群集,管理的元素不會重複而且必須不可變(Immutable)hashable(這中間還有一些要討論的東西,可以參考 物件相等性(上))。以下是 set 的幾個功能示範:
>>> admins = {'Justin', 'caterpillar'}  # 建立 set
>>> users = {'momor', 'hamini', 'Justin'}
>>> 'Justin' in admins  # 是否在站長群?
True
>>> admins & users      # 同時是站長群也是使用者群的?
{'Justin'}
>>> admins | users      # 是站長群或是使用者群的?
{'hamini', 'caterpillar', 'Justin', 'momor'}
>>> admins - users      # 站長群但不使用者群的?
{'caterpillar'}
>>> admins ^ users      # XOR
{'hamini', 'caterpillar', 'momor'}
>>> admins > users      # ∈
False
>>> admins < users 
False 
>>>

dict 型態

鍵(Key)值(Value)對應的物件,鍵物件必須是 hashable。以下是一些操作示範:
>>> passwords = {'Justin' : 123456, 'caterpillar' : 933933}
>>> passwords['Justin']
123456
>>> passwords['Hamimi'] = 970221   # 增加一對鍵值
>>> passwords
{'caterpillar': 933933, 'Hamimi': 970221, 'Justin': 123456}
>>> del passwords['caterpillar']   # 刪除一對鍵值
>>> passwords
{'Hamimi': 970221, 'Justin': 123456}
>>> passwords.items()
[('Hamimi', 970221), ('Justin', 123456)]
>>> passwords.keys()
['Hamimi', 'Justin']
>>> passwords.values()
[970221, 123456]
>>>

使用 [] 時如果指定的鍵不存在,會發生 KeyError,可以使用 dictget 方法,指定鍵不存在時傳回的預設值。例如:
>>> passwords.get('openhome', '000000')
'000000'
>>> passwords['openhome']
Traceback (most recent call last):
  File "", line 1, in 
KeyError: 'openhome'
>>>

tuple 型態

tuple 型態作用類似 list,不過 tuple 實例是不可變(Immutable),也就是一旦建立,無法對其增減元素。除了增減元素個數之外,tuplelist 操作上類似,事實上,循序結構的物件(像是字串、listtuple 等),在 Python 中共享某些操作方式。管理物件時該使用可變物件還是不可變物件?不可變物件在某些情況下,會擁有較好的效能,之後還會談到更多有關不可變物件的好處。

(在靜態定型的 Haskell 語言中,Tuple 更具效用,因為 Tuple 中的元素型態就組成了一個新的但未命名的型態。)

練習 3:Python 互動模式與直譯器指令


開啟一個終端機,鍵入 python 指令進入互動模式,接著鍵入以下指令,你會看到什麼?
  • 1 + 2
  • _                        # _ 代表最後一次執行結果
  • _ + 3
  • help()                # 進入文件查詢介面
  • len                    # 查詢 len 函式說明
  • keywords            # 查詢關鍵字有哪些
  • quit(或是 q)  # 離開文件查詢介面
  • help(len)           # 直接查詢 len 函式說明
  • Ctrl + D            # 離開互動模式
離開互動模式後,直接鍵入以下指令會看到什麼?
  • python -h
  • python -c 'print "Hello! Python!"'
  • python -c 'help(len)'
  • python -c 'import this'
-h 引數會顯示使用說明,-c 會直譯後續給定的字串文字,因此 python -c 'help(len)' 就是顯示 len 函式的說明,import this 會顯示一段文件,代表 Python 的哲學,也是發展的規範
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

if、for 與 while

流程語法中最簡單的 if..else 分支判斷,在 Python 中是這樣寫的:
from sys import argv
if len(argv) > 1:
    print 'Hello, ' + argv[1]
else:
    print 'Hello, Guest'

在 Python 中,使用 : 來做為區塊(Block)開始的標示,相同縮排則表示了相同區塊範圍的程式碼,縮排必須一致,如果想使用四個空白字元縮排,整個程式都必須是四個空白字元縮排,如果要用 Tab 縮排,整個程式都必須使用 Tab 縮排。Python 中的 if..else 也有運算式(Expression)形式,使用上就像是 C 或 Java 的三元運算子 ?:。if 條件式成立的話,會傳回 if 左邊的值,否則傳回 else 右邊的值。例如上面的程式也可以寫為:
from sys import argv
print 'Hello, ' + (argv[1] if len(argv) > 1 else 'Guest')

Python 中的 for 可用來迭代循序結構的物件。例如想將某 list 的元素都做二次方運算,收集在另一個 list 中的話,可以如下:
numbers = [10, 20, 30]
squares = []
for number in numbers:
    squares.append(number ** 2)
print squares

至於 while,一般是用在結束條件不確定的情況下。例如求最大公因數可以如下:
print 'Enter two numbers...'
m = int(raw_input('Number 1: '))
n = int(raw_input('Number 2: '))
while n != 0:
   r = m % n
   m = n
   n = r
print 'GCD: {0}'.format(m)

回頭看看這個範例:
numbers = [10, 20, 30]
squares = []
for number in numbers:
    squares.append(number ** 2)
print squares

將某 list 的元素都做二次方運算,收集在另一個 list 中的話,其實還可以使用 for 包含式:
numbers = [10, 20, 30]
print [number ** 2 for number in numbers]

這樣的寫法顯然簡潔多了。for 包含式也可以與條件式結合。例如想收集某個 list 中的奇數元素至另一 list,單純使用 for 迴圈,可以如下:
numbers = [11, 2, 45, 1, 6, 3, 7, 8, 9]
odd_numbers = []
for number in numbers:
    if number % 2 != 0:
        odd_numbers.append(number)
print odd_numbers

使用 for 包含式的程式碼就簡潔多了:
numbers = [11, 2, 45, 1, 6, 3, 7, 8, 9]
print [number for number in numbers if number % 2 != 0]

for
包含式(comprehension)也可以形式巢狀結構,例如有個元素都為 listlist,想將其中的 list 元素串起來,也就是將之平坦化,可以如下:
lts = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print [ele for lt in lts for ele in lt]

當你使用 [] 包圍住 for 包含式(comprehension) 時,會建立 list 實例,如果使用 {} 的話,可以建立 set 實例,重複的元素會自動去除。例如:
>>> {name for name in ['caterpillar', 'Justin', 'caterpillar', 'openhome']}
set(['caterpillar', 'Justin', 'openhome'])
>>>

也可以建立 dict 實例。例如:
>>> names = ['caterpillar', 'justin', 'openhome']
>>> passwds = [123456, 654321, 13579]
>>> {name : passwd for name, passwd in zip(names, passwds)}
{'justin': 654321, 'openhome': 13579, 'caterpillar': 123456}
>>> 

上例中的 zip 函式,就如名稱意義,會將兩個 list 像拉鏈一樣,兩兩相扣在一起為 tuple,這些 tuple 元素組成一個新的 list,對於 tuple 元素組成的這個 list,每個 tuple 中的一對元素再指定給 namepasswd,最後這對 namepasswd 組成 dict 的一對鍵值。

(有些人剛接觸 Python 時,不太習慣 for 包含式的寫法,可以來看看 Haskell 中如何表達數學式 S = { 2 . x | x ∈ N, x ≦ 10},Haskell 是寫為 [2 * x | x <- N, x <= 10] ,跟原本的數學式很相像,用這個方向來理解 Python 的 for 包括式,就比較能夠接受這樣的寫法。)

練習 4:使用 for 包含式


在 LAB 檔案中,有個 exercises\exercise4\exercise4-1.py,內容如下:
numbers = []
for number in range(20):
    numbers.append(str(number))
print ", ".join(numbers)

請試著使用 for 包含式來改寫它,解答可在 LAB 檔案的 solutions\exercise4\exercise4-1.py 找到。

(如果想挑戰比較難的練習,可試著使用 for 包含式來求解以下問題:找出周長為 24,每個邊長都為整數且不超過 10 的直角三角形。你可以試著看看 LAB 檔案中 exercises\exercise4\exercise4-2.py 的提示,解答可在 LAB 檔案的 solutions\exercise4\exercise4-2.py 找到)