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


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

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

list 型態

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

list 與先前介紹過的 str 享有共同的操作。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 型態是無序群集,管理的元素不會重複而且必須是 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()
dict_items([('Hamimi', 970221), ('Justin', 123456)])
>>> passwords.keys()
dict_keys(['Hamimi', 'Justin'])
>>> passwords.values()
dict_values([970221, 123456])
>>> 

在 Python 2.x 中,dict 實例的 itemskeysvalues 會傳回 list,而在 Python 3.x 中,它們會傳回 dict_itemsdict_keysdict_values,這些物件是可以進行迭代的(例如使用 for...in 進行迭代),相較於建立一個夠長的 list 來儲存這些元素,Python 3.x 的作法比較經濟,特別是在 dict 中儲存的鍵值對很多的時候。

如果你真的需要一個 list,那麼可以使用像是 list(passwords.items())list(passwords.keys())list(passwords.values()) 的方式來取得。

使用 [] 時如果指定的鍵不存在,會發生 KeyError,可以使用 dictget 方法,指定鍵不存在時傳回的預設值。例如:

>>> passwords.get('openhome', '000000')
'000000'
>>> passwords['openhome']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'openhome'
>>> 

tuple 型態

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

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

ifforwhile

流程語法中最簡單的 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(input('Number 1: '))
n = int(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']}
{'Justin', 'openhome', 'caterpillar'}
>>> 

也可以建立 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 包括式,就比較能夠接受這樣的寫法。

練習 3:使用 for 包含式

在 LAB 檔案中,有個 exercises\exercise3\exercise3-1.py,內容如下:

numbers = []
for number in range(20):
    numbers.append(str(number))
print(", ".join(numbers))

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

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