Python可以這樣玩(3):Python 序列
首先說明一下,本文章首頁出現的照片,只是預告一下後面關於 Arduino 的課程,與本文無關。所有的電路程式設計,都可以由 Python 達成。
列表(List)與列表推導式
列表(List)的建立與刪除
列表常用方法
append()、insert()、extend()
pop()、remove()、clear()
count()、index()
sort()、reverse()
內建函數對列表的操作
map()
reduce()
filter()
列表推導式
切片
使用切片取的列表部分元素
使用切片對列表元素進行增、刪、改
Python 常用的序列結構有列表、元組、字典、字串、集合等等,大致可以分為有序與無序兩類:其中列表、元組、字串屬於有序序列(有順序的),字典、集合屬於無序序列。前一個章節有討論過列表、字串、集合三個,但是在這裡會更深入的討論。
對於有序序列而言,都會支援雙向索引,第一個元素的索引為 0,第二個元素的索引為
1,依此類推。反向的話,倒數第一個元素的索引為 -1,倒數底二個元素的索引為 -2,依此類推。使用負整數作為索引是 Python 序列的一大特色,熟練之後,可以大幅提升開發效率。
列表(List)與列表推導式
列表在上一章節有簡單介紹過,是 Python 內建重要的可變序列之一,它是包含若干元素的有序連續記憶體空間。形式上,列表的所有元素放在一對中括號裡面,相鄰的元素之間以逗點分開。
在 Python 中,同一個列表中元素的資料類型可以不一樣,例如可以分別為整數、實數、字串等基本資料類型,或者是列表、元組、字典、集合以及其他自訂類型的物件。下面幾種都是合法的列表物件:
隨堂練習
玩擲骰子遊戲,一次值四個骰子,共擲三次
請問如何用列表表示?
>>>
列表(List)的建立與刪除
以 = 直接將列表賦予值給變數,即可建立列表物件,例如:
>>> a_list = [1, 2, 3, 4, 5]
>>> a_list
[1, 2, 3, 4, 5]
>>> b_list = [] # 空值
>>> b_list
[]
>>>
也可以使用 list() 函數將元組、range 物件、字串、字典、集合等資料類別轉換為列表,請先看下面的例子:
字典比較特殊,如果直接用 list() 轉換,只會轉換鍵,如果要同時轉換鍵:值,則需使用 items()。
在 Python 社群中,習慣將
list() 還有後面很快就會學到的 tuple()、set()、dict() 等函數稱為 “工廠函數”,因為這些函數可以生產新的資料型態。
建立列表之後,就可以使用整數作為索引存取其中的元素,其中0代表第一個,前面有說明過:
當不再使用列表時,可使用 del 刪除。
隨堂實作
填空題:
>>> x = [1, 2, 3]
>>> x
_________
>>> del x[1]
>>> x
_________
>>> del x
>>> x
_________
列表常用方法
首先說明一下函數與方法的不同,雖然他們看起來很像,但是呼叫方式卻不同,函數是以
function() 的方式呼叫,方法則是包裝在物件之內的函數,所以是以 object.method() 的方式呼叫。熟悉物件導向程式設計(OOP)的人,應該駕輕就熟。物件裡面會包含兩種東西,一是變數(variable),一是函數(function),物件內的變數稱之為屬性(attribute),物件內的函數稱之為方法(method)。
還記得如何使用 help() 來求助嗎? help() 括號裡面的參數可以是一個物件,輸入 help([]),就可以得到所有關於 list 的方法,如下圖:
複製貼上再整理一下,就可以當成我們的教材,刪除不常用的,留下常用的,如下表,這裡出現的都是方法,所以可以看到(以reverse() 為例)其呼叫方式為 L.reverse():
>>> help([])
Help on list object:
class list(object)
|
list() -> new empty list
|
list(iterable) -> new list initialized from iterable's items
|
Methods defined here:
|
append(...)
|
L.append(object) -> None -- append object to end
|
clear(...)
|
L.clear() -> None -- remove all items from L
|
copy(...)
|
L.copy() -> list -- a shallow copy of L
|
count(...)
|
L.count(value)->integer--return number of occurrences of value
|
extend(...)
|
L.extend(iterable) -> None -- extend list by appending elements
| from
the iterable
|
index(...)
|
L.index(value, [start, [stop]]) -> integer -- return first
| index
of value.
|
Raises ValueError if the value is not present.
|
insert(...)
|
L.insert(index, object) -- insert object before index
|
pop(...)
|
L.pop([index]) -> item -- remove and return item at index
| (default
last).
|
Raises IndexError if list is empty or index is out of range.
|
remove(...)
|
L.remove(value) -> None -- remove first occurrence of value.
|
Raises ValueError if the value is not present.
|
reverse(...)
|
L.reverse() -- reverse *IN PLACE*
|
sort(...)
|
L.sort(key=None, reverse=False) -> None--stable sort *IN PLACE*
| -------------------------------------------------------------------
一定要練習看英文的說明,這樣可以快速地得到協助,又可以加強英文能力,一舉兩得。
append()、insert()、extend()
這三種方法都能增加元素列表物件,請自行參考上面的 help 說明。這三種方法都屬於原地操作,也就是說不會改變列表的位址。下面直接用例子說明用法:
>>>
x = [1, 2, 3]
>>>
id(x)
1856880275848
>>>
x.append(4)
>>>
x
[1, 2,
3, 4]
>>>
id(x)
1856880275848
>>>
x.insert(0, 0) #
在第一個位置插入 0
>>>
x
[0, 1,
2, 3, 4]
>>>
id(x)
1856880275848
>>>
x.extend([5, 6, 7, 8])
>>>
x
[0, 1,
2, 3, 4, 5, 6, 7, 8]
>>>
id(x)
1856880275848
>>>
我們也可以使用運算子 + 和 * 來達到增加列表元素的目的,但這兩個運算子不屬於原地操作,而是返回新的列表,如下:
>>>
x = [1, 2, 3]
>>>
id(x)
1856880279496
>>>
x = x + [4]
>>>
x
[1, 2,
3, 4]
>>>
id(x)
1856870756488
>>>
x = x * 2
>>>
x
[1, 2,
3, 4, 1, 2, 3, 4]
>>>
id(x)
1856880017544
>>>
pop()、remove()、clear()
這三個方法則是用來刪除列表中的元素,其中pop()會刪除並返回指定位置(預設是最後一個)的元素,remove()則刪除列表中第一個與指定值相等的元素,clear()是用來清空列表,這三種方法也屬於原地操作。
實機操作
>>> x =
[1, 2, 3, 4, 5, 6, 7]
>>>
x.pop()
________
>>>
x.pop(0)
________
>>>
x.clear()
>>> x
________
>>> x = [1,
2, 1, 1, 2]
>>>
x.remove(2)
>>> x
________
>>> del
x[3] # 並不是 list 的方法,為一函數
>>> x
________
count()、index()
列表方法 count() 返回列表中指定元素出現的次數,index() 則返回指定元素在列表中首次出現的位置,如果元素不存在則拋出異常。
實機操作
>>> x =
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> x.count(3)
________
>>> x.count(5)
________
>>> x.index(2)
________
>>> x.index(4)
________
>>> x.index(5)
________
>>> 5 in x
________
>>> 3 in x
________
sort()、reverse()
這兩個方法,我們會跟另外兩個函數 sorted()、reversed() 做比較,區別方法與函數的不同,以及in-place 與非 in-place 的不同。
實機操作
>>> x =
list(range(11)
>>> import
random
>>> _______.shuffle(x)
>>> x
___________________________________________________
>>> x.reverse()
>>> x
___________________________________________________
>>>
x.sort()
>>> x
___________________________________________________
>>>
x.sort(key=str)
>>> x
___________________________________________________
sorted()與reversed() 就完全不同了,首先它們是函數而不是list的方法,所以呼叫方式不同,其次它們在排序或逆序之後,會傳回新的序列,不影響原本序列。其中 reversed()更特別,它會傳回一個逆向排序的反覆運算物件,由於傳回的是一個物件,所以我們不會直接看到序列的元素,需要額外處理才看的見:
實機操作
>>> x =
list(range(11)
>>> import
random
>>> _______.shuffle(x)
>>> x
___________________________________________________
>>> reversed(x)
___________________________________________________
>>> list(reversed(x))
___________________________________________________
>>> sorted(x)
___________________________________________________
>>> sorted(x,
key=str)
___________________________________________________
>>> x
___________________________________________________
排序方法中的 key 參數,可以實現更複雜的排序動作,我們用個實際的劇本來練習。假設有兩個隊伍參加線上遊戲,分別是A隊與B對,每隊有三個人,以及每個人的得分:
A 隊:
Bob, 86分
Andrew, 95分
Mandy, 83分
B 隊:
Ruby, 89分
Elvis, 91分
Peter, 77分
我們嘗試使用二維列表來記錄:
實機操作
>>>
gameresult = [['Bob', 86, 'A'],
['Andrew', 95, 'A'],
['Ruby', 89, 'B'],
['Elvis', 91, 'B'],
['Mandy', 83, 'A'],
['Peter', 77, 'B']]
>>>
gameresult
[['Bob', 86, 'A'],
['______', 95, 'A'], ['Ruby', ____, '__'], ['______', 91, 'B'], ['Mandy', 83,
'A'], ['______', 77, 'B']]
>>> from
operator import itemgetter
>>>
sorted(gameresult, key=itemgetter(2)) #按照子列表第三個元素排序
[['Bob', 86, 'A'],
['Andrew', 95, 'A'], ['_____', ___, '__'], ['Ruby', 89, 'B'], ['Elvis', 91,
'B'], ['______', ___, '__']]
>>>
sorted(gameresult, key=itemgetter(2,0))#先第三個元素再第一個元素
[['______', ___, '__'],
['Bob', 86, 'A'], ['Mandy', 83, 'A'], ['_______', ___, '__'], ['Peter', 77,
'B'], ['Ruby', 89, 'B']]
問題:先按照隊伍排序(順序不拘),再按照分數高低(由高到低)排序
(提示 reverse=True 降冪排序)
>>>
_______________________________________________________
當然,Python 還有更複雜的排序方式,我們先練習到這。
內建函數對列表的操作
除了列表物件本身的方法之外,很多 Python 的內建函數也可以操作列表,例如 max()、min()、sum()等函數,這三個之前說明過。而len()則是傳回列表元樹的個數。這四個比較簡單,請自行練習。
接下來說明兩個與列表轉換為元組的相關函數,zip() 和 enumerate(),zip()函數重新組合多個列表的元素成為元組,並返回包含這些元組的 zip物件;enumerate() 函數返回包含若干索引和值的反覆運算物件。看說明想必會昏倒,我們直接看例子:
>>>
from random import shuffle
>>>
x = list(range(11))
>>>
y = list(range(11))
>>>
shuffle(x)
>>>
x
[5, 8,
7, 4, 10, 6, 1, 9, 3, 2, 0]
>>>
y
[0, 1,
2, 3, 4, 5, 6, 7, 8, 9, 10]
>>>
zip(x, y)
<zip
object at 0x0000024155383C88>
>>>
list(zip(x, y))
[(5, 0),
(8, 1), (7, 2), (4, 3), (10, 4), (6, 5), (1, 6), (9, 7), (3, 8), (2, 9), (0,
10)]
>>>
enumerate(x)
<enumerate
object at 0x0000024155370B40>
>>>
list(enumerate(x))
[(0, 5),
(1, 8), (2, 7), (3, 4), (4, 10), (5, 6), (6, 1), (7, 9), (8, 3), (9, 2), (10,
0)]
>>>
zip() 把兩個列表中的元素打包成元組物件,enumerate()
則把一個列表的元素與自行加在前面的index 數字打包成元組物件。因為物件無法被看到,所以我們利用 list() 將其轉換成列表之後呈現。至於什麼是元組(tuple),下一個章節就會說明。
map()
接下來要談的是 Python 最強悍的地方,我們會漸漸發現 Python 與其它程式語言的不同之處。在 Python 中,map()、reduce()、filter() 是函數式程式設計的重要呈現形式。
內建函數 map() 能將一個函數依序作用到序列或反覆運算器物見的每個元素,然後返回 map 物件作為結果,其中每個元素是元序列元素經過該函數處理後的結果,不對原序列作任何修改。
光看敘述我敢保證一定看不懂,不用擔心,我們透過實機操作來了解其意義。
實機操作
>>> x =
list(range(5))
>>> x
[0, 1, 2, 3, 4]
>>>
map(str, x)
<map object at
0x0000024155382D30>
>>>
list(map(str, x))
[_____, _____, _____,
_____, _____]
>>> def
add5(v):
return v+5
>>>
list(map(add5, x))
[___, ___, ___, ___,
___]
>>> x
[0, 1, 2, 3, 4]
>>> y =
list(range(5, 10))
>>> y
[5, 6, 7, 8, 9]
>>> def
add(a, b):
return a+b
>>>
list(map(add, x, y))
[___, ___, ___, ___,
___]
>>>
list(map(lambda a,b:a+b, x, y))
[___, ___, ___, ___,
___]
>>> [add(a,
b) for a, b in zip(x, y)]
[___, ___, ___, ___,
___]
>>>
請注意最後兩行指令,一個使用 lambda 語法,一個使用 for 語法,這就是 Python 沒有最簡、只有更簡的特性。
Lambda 語法直接把 “結果=f(參數)” 這樣的函數寫法寫成 “lambda 參數:結果”,for 語法在這裡則稱為列表推導式,後面會進一步說明。
試著使用學習英文的語法分析方法,分析這兩個指令的執行步驟。
reduce()
標準庫 functools 中的函數 reduce() 會將一個接收2個參數的函數,以遞減的方式從左到右依序作用到一個數列中的所有元素。用白話文說,就是先以第一個和第二個元素作為參數丟給函數執行,執行的結果再和第三個元素當成兩個新的參數丟給函數執行,依此類推,一直到所有元素執行完畢為止。元素會越來越遞減,最後剩下一個結果。
接續上面說明 map() 的例子,繼續往下實作,因為我們會用到前面自定義的 add()函數:
實機操作
>>> from
functools import reduce
>>> seq = [1,
2, 3, 4, 5]
>>>
reduce(add, seq)
_____
>>> reduce(lambda
a, b: a+b, seq)
_____
>>>
其實就是在計算 1 加到 5 的總和。
filter()
內建函數 filter() 將一個單參數函數作用到序列,並返回該序列中使得該函數的返回值為 True 的所有元素組成的 filter 物件。就像是一個過濾器。
我們在玩線上遊戲的時候通常要取一個別名,如果系統對別名的命名方式規定必須是文字或者是數字的時候,就可以用過濾器把合格的別名挑出來:
實機操作
>>> seq =
['foo', 'xbox360', '!!!', '$_$', 'husky']
>>> def
isok(x):
return x.isalnum()
>>>
list(filter(isok, seq))
[__________, __________,
__________]
>>> [x for
x in seq if x.isalnum()]
['foo', 'xbox360',
'husky']
>>>
list(filter(lambda x: x.isalnum(), seq))
['foo', 'xbox360',
'husky']
>>>
請進一步說明 lambda 與 for 的執行步驟:
Lambda:
For:
列表推導式
前面出現過很多列表推導式的例子,其實在邏輯上它相當於一個迴圈,只是形式更加簡潔,例如:
>>>
a_list = [x*x for x in range(10)]
>>>
a_list
[0, 1,
4, 9, 16, 25, 36, 49, 64, 81]
>>>
b_list = []
>>>
for x in range(10):
b_list.append(x*x)
>>>
b_list
[0, 1,
4, 9, 16, 25, 36, 49, 64, 81]
>>>
隨堂測驗 設計黑白棋棋盤
大部分的人應該有玩過黑白棋,沒有玩過應該也有看過
黑白棋的棋盤是一個 8x8 大小的方格盤
通常我們會使用 二維矩陣 來表示
在 Python 中可以用 二維序列 表示成:
>>> board
= [[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]]
>>> board
[[0, 0, 0, 0, 0, 0,
0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0,
0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0,
0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
>>>
請用 for 完成(提示如下):
>>> [[ 0
for …
>>>
切片
切片也是 Python 序列的重要操作之一,可以這樣來解釋,如果有學過 Basic 語言的人一定寫過 Basic 的 for 迴圈,FOR I = 1 TO 10 STEP 1,這個迴圈裏面包含了三個東西,起始值,結束值(Python則不包含),與間隔。
Python 的切片則寫成這樣:[起始值:結束值(但不包含):間隔]
回想一下我們常常用到的 range(10),我們用 [i for i in range(10)] 來建立一個 0 到 9 的列表: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]。其實就等同於 [i for i in range(0,10,1)],在 range() 括號裡面同樣也接受三個數字:
起始值(預設0) : 結束值(但不包含) : 間隔(預設1)
這樣所有的謎題應該就解開了吧!
隨堂測驗 建立一個 [1, 3, 5, 7, 9]列表
>>> [ i
for __________________ ]
>>>
使用切片取的列表部分元素
直接使用範例教學:
>>>
mylist = [i for i in range(3,18,2)]
>>>
mylist
[3, 5,
7, 9, 11, 13, 15, 17]
>>>
mylist[::]
[3, 5,
7, 9, 11, 13, 15, 17]
>>>
mylist[::-1]
[17, 15,
13, 11, 9, 7, 5, 3]
>>>
mylist[::2]
[3, 7,
11, 15]
>>>
mylist[1::2]
[5, 9,
13, 17]
>>>
mylist[3:6]
[9, 11,
13]
>>>
mylist[0:100]
[3, 5,
7, 9, 11, 13, 15, 17]
>>>
mylist[100:]
[]
>>>
mylist[100]
Traceback
(most recent call last):
File "<pyshell#101>", line 1,
in <module>
mylist[100]
IndexError:
list index out of range
>>>
使用切片對列表元素進行增、刪、改
利用切片能夠快速地實現很多目的,直接看下面的執行結果:
>>>
aList = [3,5,7]
>>>
aList[len(aList):]
[]
>>>
aList
[3, 5,
7]
>>>
aList[len(aList):] = [9]
>>>
aList
[3, 5,
7, 9]
>>>
aList[:3] = [1,2,3]
>>>
aList
[1, 2,
3, 9]
>>>
aList[:3] = []
>>>
aList
[9]
>>>
aList = list(range(10))
>>>
aList
[0, 1,
2, 3, 4, 5, 6, 7, 8, 9]
>>>
aList[::2] = [0] * (len(aList)//2)
>>>
aList
[0, 1,
0, 3, 0, 5, 0, 7, 0, 9]
>>>
aList[3:3] = [4,5,6]
>>>
aList
[0, 1,
0, 4, 5, 6, 3, 0, 5, 0, 7, 0, 9]
>>>
aList[20:30] = [3] * 2
>>>
aList
[0, 1,
0, 4, 5, 6, 3, 0, 5, 0, 7, 0, 9, 3, 3]
>>>
上面的範例如果有任何看不懂的地方,請分段執行。請務必確認全部都弄清楚了之後,再繼續往下。
一般人初學程式語言,通常會從 BASIC開始,當從 BASIC語言進入C語言的世界的時候,最令人頭疼的觀念,就是指標,然後就開始傳值、傳址,搞得初學者一頭霧水。接下來我們要導入 Python 的指標概念,在C語言指標叫做
pointer,在Python 就叫做id。
從最簡單的概念開始,當我們把變數a設定為 5 這個數值的時候,用Python 會寫成 a = 5,這點完全沒有問題,對人類而言,我們看到的是a裡面存著5這個數字,但是對 Python而言,則是5 存在 a 所指的記憶體位址上面。
當我們接著指定 b = a 的時候,我們會認為 a 的值是 5 以及 b 的值是 5,焦點會在值上面,但是Python是把 a, b 指向同一個記憶體位置上面,焦點在位址上面。從例子來看:
>>>
a = 5
>>>
id(a)
1779396192
>>>
b = a
>>>
id(b)
1779396192
>>>
b = 5
>>>
id(b)
1779396192
>>>
b = 6
>>>
id(b)
1779396224
>>>
c=5
>>>
d=5
>>>
id(c)
1779396192
>>>
id(d)
1779396192
>>>
d=7
>>>
id(d)
1779396256
>>>
不要小看這範例,它詳細說明了 Python 是如何處理記憶體的,不同的變數名稱,即使我們在不同地方指定同一個數值,它們還是指向同一個記憶體位址,直到另一個變數指定了不同數值之後,位址才會改變。
為何要說明這些?因為稍後我們就要討論切片返回的是列表元素的淺複製,與列表物件的直接賦予值是不一樣的:
>>>
aList = [3,5,7]
>>>
bList = aList
>>>
id(aList)
2479625896072
>>>
id(bList)
2479625896072
>>>
aList == bList # 值相同
True
>>>
aList is bList # 同一個物件
True
>>>
bList[1] = 8
>>>
aList
[3, 8,
7]
>>>
cList = aList[::] # 切片,淺複製
>>>
aList == cList
True
>>>
aList is cList # 不同物件
False
>>>
cList[1] = 9
>>>
cList
[3, 9,
7]
>>>
aList
[3, 8,
7]
>>>
不過數字跟列表有一點不同,如下:
>>>
a = 5
>>>
b = 5
>>>
a is b
True
>>>
a = [1,2,3]
>>>
b = [1,2,3]
>>>
a is b
False
>>>
原因應該是列表太過於龐大,在分開指定值的時候,沒有必要一一比對是否每個元素都相同,反而沒有效率。Python 針對整數、字元、字串資料類別的變數會指向同一個位址,但像是浮點數、序列則不會。
隨堂練習:
回覆刪除[ i for i in range(1,10,2)]
[1,3,5,7,9]
離 Arduino 又進了一步
回覆刪除