目錄| 上一節 (2.3 格式化) | 下一節 (2.5 Collections模組)
2.4 序列
序列資料型別
Python 有三種序列資料型別。
- 字串:如
'Hello'
。字串是字元序列 - 列表:如
[1, 4, 5]
。 - 元組:如
('GOOG', 100, 490.1)
。
所有的序列都是有序的,由整數進行索引,並且具有長度。
a = 'Hello' # String
b = [1, 4, 5] # List
c = ('GOOG', 100, 490.1) # Tuple
# Indexed order
a[0] # 'H'
b[-1] # 5
c[1] # 100
# Length of sequence
len(a) # 5
len(b) # 3
len(c) # 3
序列可以通過重複操作符 * 進行重複:s * n
。
>>> a = 'Hello'
>>> a * 3
'HelloHelloHello'
>>> b = [1, 2, 3]
>>> b * 2
[1, 2, 3, 1, 2, 3]
>>>
相同型別的序列可以通過加號 + 進行拼接:s + t
。
>>> a = (1, 2, 3)
>>> b = (4, 5)
>>> a + b
(1, 2, 3, 4, 5)
>>>
>>> c = [1, 5]
>>> a + c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple
切片
切片是指著從序列中提取子序列。切片的語法為 s[start:end]
。 start
和 end
是想要的子序列的索引。
a = [0,1,2,3,4,5,6,7,8]
a[2:5] # [2,3,4]
a[-5:] # [4,5,6,7,8]
a[:3] # [0,1,2]
- 索引
start
和end
必須是整數。 - 切片不包括結尾值。這就像數學上的半開區間。
- 如果省略索引,則它們預設為序列的開頭或結尾。
切片與重新賦值
在列表上,切片可以被重新賦值和刪除。
# Reassignment
a = [0,1,2,3,4,5,6,7,8]
a[2:4] = [10,11,12] # [0,1,10,11,12,4,5,6,7,8]
注意:重新賦值的切片不需要具有相同的長度。
# Deletion
a = [0,1,2,3,4,5,6,7,8]
del a[2:4] # [0,1,4,5,6,7,8]
序列的縮減
有一常見的函式用於把序列縮減為單個值。
>>> s = [1, 2, 3, 4]
>>> sum(s)
10
>>> min(s)
1
>>> max(s)
4
>>> t = ['Hello', 'World']
>>> max(t)
'World'
>>>
迭代序列
可以使用 for 迴圈對序列中的元素進行迭代。
>>> s = [1, 4, 9, 16]
>>> for i in s:
... print(i)
...
1
4
9
16
>>>
在迴圈的每次迭代中,會獲取一個新的項來處理。這個新的值會被放到迭代變數中。在此示例中,迭代變數為 x:
for x in s: # `x` is an iteration variable
...statements
在每次迭代中,迭代變數的先前值會被覆蓋(如果有)。迴圈結束後,迭代變數保留最後一個值。
break 語句
可以使用 break
語句提前跳出迴圈。
for name in namelist:
if name == 'Jake':
break
...
...
statements
當 break
語句執行時,它退出迴圈並且進入下一個語句。break
語句僅應用於最內部的迴圈。如果此迴圈在另一個迴圈的內部,那麼 break
不會中斷外部迴圈。
continue 語句
要跳過一個元素並且進入到下一個,請使用 continue
語句。
for line in lines:
if line == '\n': # Skip blank lines
continue
# More statements
...
如果當前項不重要或者是在處理時需要忽略,那麼使用 continue
語句很有用。
遍歷整數
如果需要計數,請使用 range()
函式。
for i in range(100):
# i = 0,1,...,99
range() 函式的語法是range([start,] end [,step])
。
for i in range(100):
# i = 0,1,...,99
for j in range(10,20):
# j = 10,11,..., 19
for k in range(10,50,2):
# k = 10,12,...,48
# Notice how it counts in steps of 2, not 1.
- 不包括結尾值。這與切片類似。
start
是可選的 , 預設值是0
。step
是可選的,預設值是1
。- 當需要的值時候
range()
才計算值,實際上,它不儲存大範圍的數。
enumerate() 函式
enumerate
函式為迭代新增一個額外的計數值。
names = ['Elwood', 'Jake', 'Curtis']
for i, name in enumerate(names):
# Loops with i = 0, name = 'Elwood'
# i = 1, name = 'Jake'
# i = 2, name = 'Curtis'
一般格式為enumerate(sequence [, start = 0])
,start
是可選的,一個很好的使用示例:讀取檔案時跟蹤行數。
with open(filename) as f:
for lineno, line in enumerate(f, start=1):
...
enumerate
可以看成以下語句的簡寫:
i = 0
for x in s:
statements
i += 1
使用 enumerate
函式可以減少輸入,執行速度也稍快一些。
For 與元組
可以迭代多個變數:
points = [
(1, 4),(10, 40),(23, 14),(5, 6),(7, 8)
]
for x, y in points:
# Loops with x = 1, y = 4
# x = 10, y = 40
# x = 23, y = 14
# ...
當使用多個變數時,每個元組被拆包為一組迭代變數。變數的數目必須與每個元組中的項數匹配。
zip() 函式
zip
函式採用多個序列,並且生成將它們組合在一起的迭代器。
columns = ['name', 'shares', 'price']
values = ['GOOG', 100, 490.1 ]
pairs = zip(columns, values)
# ('name','GOOG'), ('shares',100), ('price',490.1)
要獲得結果,必須進行迭代。可以如先前所示的那樣使用多個變數對元組進行拆包。
for column, value in pairs:
...
zip
函式的常見用法是建立用於構造字典的鍵值對。
d = dict(zip(columns, values))
練習
練習 2.13:計數
嘗試一些基本的計數示例:
>>> for n in range(10): # Count 0 ... 9
print(n, end=' ')
0 1 2 3 4 5 6 7 8 9
>>> for n in range(10,0,-1): # Count 10 ... 1
print(n, end=' ')
10 9 8 7 6 5 4 3 2 1
>>> for n in range(0,10,2): # Count 0, 2, ... 8
print(n, end=' ')
0 2 4 6 8
>>>
練習 2.14:更多序列操作
互動地試驗一些序列縮減操作。
>>> data = [4, 9, 1, 25, 16, 100, 49]
>>> min(data)
1
>>> max(data)
100
>>> sum(data)
204
>>>
嘗試遍歷資料。
>>> for x in data:
print(x)
4
9
...
>>> for n, x in enumerate(data):
print(n, x)
0 4
1 9
2 1
...
>>>
有時候,for
語句,len()
和 range()
函式被初學者用於一些可怕的程式碼片段中,這些程式碼看起來像來自於古老的 C 程式。
>>> for n in range(len(data)):
print(data[n])
4
9
1
...
>>>
不要那樣做。閱讀這些程式碼不僅辣眼睛,而且記憶體效率低,執行慢。如果想要迭代資料,使用普通的for
迴圈即可。如果碰巧因為某些原因需要使用索引,請使用 enumerate()
函式。
練習 2.15:enumerate() 函式使用示例
回想一下,Data/missing.csv
檔案包含一個股票投資組合的資料,但是有一些行缺少值。請使用 enumerate()
函式修改 pcost.py
程式,以便在遇到錯誤的輸入時,列印帶有警告資訊的行號。
>>> cost = portfolio_cost('Data/missing.csv')
Row 4: Couldn't convert: ['MSFT', '', '51.23']
Row 7: Couldn't convert: ['IBM', '', '70.44']
>>>
為此,需要修改部分程式碼。
...
for rowno, row in enumerate(rows, start=1):
try:
...
except ValueError:
print(f'Row {rowno}: Bad row: {row}')
練習 2.16:使用 zip() 函式
在 Data/portfolio.csv
檔案中,第一行包含列標題。在之前所有程式碼中,我們把它丟棄了。
>>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'shares', 'price']
>>>
但是,如果標題要用於其它有用的事情呢?這就涉及到 zip()
函式了。首先,嘗試把檔案標題和資料行配對。
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>> list(zip(headers, row))
[ ('name', 'AA'), ('shares', '100'), ('price', '32.20') ]
>>>
請注意 zip()
函式是如何把列標題與列值配對。在這裡,我們使用 list()
函式把結果轉換為列表,以便檢視。通常,zip()
函式建立一個必須由 for 迴圈使用的迭代器。
這種配對是構建字典的中間步驟。現在嘗試:
>>> record = dict(zip(headers, row))
>>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'}
>>>
在處理大量資料檔案時,這種轉換是最有用的技巧之一。例如,假設需要使 pcost.py
程式處理各種輸入檔案,但是不考慮名稱,份額,價格所在列的編號。
修改 pcost.py
程式中的 portfolio_cost()
,使其看起來像這樣:
# pcost.py
def portfolio_cost(filename):
...
for rowno, row in enumerate(rows, start=1):
record = dict(zip(headers, row))
try:
nshares = int(record['shares'])
price = float(record['price'])
total_cost += nshares * price
# This catches errors in int() and float() conversions above
except ValueError:
print(f'Row {rowno}: Bad row: {row}')
...
現在,在一個完全不同的資料檔案 Data/portfoliodate.csv
(如下所示)上嘗試 portfolio_cost() 函式。
name,date,time,shares,price
"AA","6/11/2007","9:50am",100,32.20
"IBM","5/13/2007","4:20pm",50,91.10
"CAT","9/23/2006","1:30pm",150,83.44
"MSFT","5/17/2007","10:30am",200,51.23
"GE","2/1/2006","10:45am",95,40.37
"MSFT","10/31/2006","12:05pm",50,65.10
"IBM","7/9/2006","3:15pm",100,70.44
>>> portfolio_cost('Data/portfoliodate.csv')
44671.15
>>>
如果操作正確,會發現程式仍然能夠正常執行,即使資料檔案的列格式與之前的完全不同,這很酷!
此處所做的更改是微妙的,但是卻意義重大。新版的 portfolio_cost()
可以讀取任何 CSV 檔案,並從中選擇需要的值,而不是硬編碼去讀取單個固定檔案格式。只要檔案有必要的列,程式碼就能正常執行。
修改在 2.3 節編寫的 report.py
程式,以便能夠使用相同的技術挑選出列標題。
嘗試以 Data/portfoliodate.csv
檔案作為輸入,執行 report.py
程式,並觀察是否生成和之前一樣的答案。
練習 2.17:翻轉字典
字典將鍵對映到值。例如,股票價格字典。
>>> prices = {
'GOOG' : 490.1,
'AA' : 23.45,
'IBM' : 91.1,
'MSFT' : 34.23
}
>>>
如果使用字典的 items()
方法,那麼可以獲取到鍵值對 (key,value)
:
>>> prices.items()
dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
>>>
但是,如果想要獲取 (value, key)
鍵值對列表呢?
提示:使用 zip()
函式。
>>> pricelist = list(zip(prices.values(),prices.keys()))
>>> pricelist
[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]
>>>
為什麼這樣操作?首先,這允許對字典資料執行確切型別的資料處理。
>>> min(pricelist)
(23.45, 'AA')
>>> max(pricelist)
(490.1, 'GOOG')
>>> sorted(pricelist)
[(23.45, 'AA'), (34.23, 'MSFT'), (91.1, 'IBM'), (490.1, 'GOOG')]
>>>
其次,這也說明了元組的一個重要特徵,當在比較中使用元組時,從第一項開始,逐元素進行比較,類似於字串中字元與字元逐個比較。
zip()
函式經常應用於需要從不同的地方把資料進行配對。例如,為了使用已命名的值構建字典,將列名和列值進行配對。
請注意,zip()
函式不限於一對。例如,可以使用任意數量的列表作為輸入。
>>> a = [1, 2, 3, 4]
>>> b = ['w', 'x', 'y', 'z']
>>> c = [0.2, 0.4, 0.6, 0.8]
>>> list(zip(a, b, c))
[(1, 'w', 0.2), (2, 'x', 0.4), (3, 'y', 0.6), (4, 'z', 0.8))]
>>>
另外,請注意,一旦最短的輸入序列耗盡,zip()
函式將會停止。
>>> a = [1, 2, 3, 4, 5, 6]
>>> b = ['x', 'y', 'z']
>>> list(zip(a,b))
[(1, 'x'), (2, 'y'), (3, 'z')]
>>>