owned this note
owned this note
Published
Linked with GitHub
###### tags: `Python 入門`
串列 (list) 、字串 (string) 與元組 (tuple)
====
在前面的章節中, 當我們使用內建函式 `dir` 查詢物件的特徵清單, 以及藉由 `glob` 模組的 `glob` 函式查詢檔案名稱清單時, 都曾提過傳回的是『**串列 (list)**』類別的物件, 不過當時我們只著重在清單內容, 並沒有多介紹有關串列的細節, 在這一章中, 我們就回過頭來進一步說明串列, 並且介紹用來表示一段文字的**字串 (string)** 物件。
## 串列 (list)
在上一章中查詢檔案清單時, 所得到的結果如下:
```Python!
>>> import glob
>>> glob.glob("*.*")
['blink.py', 'Client.java', 'format.py', 'key.py', 'keyboard_piano.py', 'lab13.py', 'MyServerSocket.java', 'test.py', 'testfile.py', 'weather.py']
>>>
```
這個以成對的中括號 `[` `]` 包起來的就是**串列**, 你可以先想像成裡頭可以依序放置多個物件, 例如:
```python
>>> primes = [1, 2, 3, 5, 7]
>>> type(primes)
<class 'list'>
```
以逗號區隔的就是一個個依序放入串列中的物件, 逗號之後的空格只是為了彰顯區隔, 不空也沒有關係。實際上串列就像是名牌的活動組合櫃, 裡面放置的是**一個個沒有名稱的名牌**, 每個名牌各自綁到對應的物件:
```graphviz
digraph object {
var1 [label="<name> primes" shape=record];
obj [label="<f0>|<f1>|<f2>|<f3>|<f4>" shape=Mrecord];
obj0 [label="{1|}" shape=Mrecord];
obj1 [label="{2|}" shape=Mrecord];
obj2 [label="{3|}" shape=Mrecord];
obj3 [label="{5|}" shape=Mrecord];
obj4 [label="{7|}" shape=Mrecord];
var1:name -> obj
obj:f0->obj0
obj:f1->obj1
obj:f2->obj2
obj:f3->obj3
obj:f4->obj4
}
```
### 索引
既然串列中的個別名牌沒有名稱, 那我們如何取出對應的各別物件呢?串列是以『**索引 (index)**』來標示個別的名牌:
```graphviz
digraph object {
var1 [label="<name> primes" shape=record];
obj [label="<f0>0|<f1>1|<f2>2|<f3>3|<f4>4|<f5>5" shape=Mrecord];
obj0 [label="{1|}" shape=Mrecord];
obj1 [label="{2|}" shape=Mrecord];
obj2 [label="{3|}" shape=Mrecord];
obj3 [label="{5|}" shape=Mrecord];
obj4 [label="{7|}" shape=Mrecord];
var1:name -> obj
obj:f0->obj0
obj:f1->obj1
obj:f2->obj2
obj:f3->obj3
obj:f4->obj4
}
```
**索引**是從 0 開始循序的整數, 最後的索引 5 表示串列的結尾。要取得串列中某個物件, 必須使用一對中括號加上對應的索引, 例如:
```python
>>> primes[2] # 索引位置 2 的名牌對應的是 3
3
>>> primes[0] # 索引位置 0 的名牌對應的是 1
1
>>> primes[5] # 索引位置 5 超出範圍, 沒有對應的物件
Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
primes[5]
IndexError: list index out of range
```
你可以看到 `primes[5]` 因為已經是串列結尾, 沒有對應的物件, 所以會出現錯誤訊息 告訴你超過索引範圍了。
### 從尾端往回的負索引
你也可以從串列尾端往回索引, 這時索引是從 -1 開始:
```graphviz
digraph object {
var1 [label="<name> primes" shape=record];
obj [label="{0|<f0>-5}|{1|<f1>-4}|{2|<f2>-3}|{3|<f3>-2}|{4|<f4>-1}|<f5>5" shape=Mrecord];
obj0 [label="{1|}" shape=Mrecord];
obj1 [label="{2|}" shape=Mrecord];
obj2 [label="{3|}" shape=Mrecord];
obj3 [label="{5|}" shape=Mrecord];
obj4 [label="{7|}" shape=Mrecord];
var1:name -> obj
obj:f0->obj0
obj:f1->obj1
obj:f2->obj2
obj:f3->obj3
obj:f4->obj4
}
```
使用負數索引的方式與正數索引是一模一樣的:
```python
>>> primes[-1] # 索引位置 -1 對應的物件是 7
7
>>> primes[-3] # 索引位置 -3 對應的物件是 3
3
```
### 用索引指定區間
你也可以使用兩個索引來指定區間, 這樣可以建立一個新的串列, 新串列內的個別名牌會繫結到區間內的個別物件, 例如:
```python
>>> section = primes[1:3]
>>> section
[2, 3]
```
指定區間時要用 `:` 區隔頭尾端的索引, 頭端的索引位置要在尾端的索引位置前面, 否則會出錯。另外, Python 中指定的區間都是**不包含尾端索引位置**的, 所以上例中指定的 `[1:3]` 取得的結果只有原本索引位置 1 到 2 的物件:
```graphviz
digraph object {
var1 [label="<name> primes" shape=record];
obj [label="{0|<f0>-5}|{1|<f1>-4}|{2|<f2>-3}|{3|<f3>-2}|{4|<f4>-1}|<f5>5" shape=Mrecord];
obj0 [label="{<f0>1|<o0>}" shape=Mrecord];
obj1 [label="{<f1>2|<o1>}" shape=Mrecord color=blue penwidth=2];
obj2 [label="{<f2>3|<o2>}" shape=Mrecord color=blue penwidth=2];
obj3 [label="{<f3>5|<o3>}" shape=Mrecord];
obj4 [label="{<f4>7|<o4>}" shape=Mrecord];
objN [label="{<f0>0|-2}|{<f1>1|-1}|<f2>2" shape=Mrecord color=blue penwidth=2];
varN [label="<name> section" shape=record color=blue penwidth=2];
var1:name -> obj
obj:f0->obj0
obj:f1->obj1 [color=blue penwidth=2]
obj:f2->obj2 [color=blue penwidth=2]
obj:f3->obj3
obj:f4->obj4
obj1->objN:f0 [dir=back color=blue penwidth=2]
obj2->objN:f1 [dir=back color=blue penwidth=2]
objN->varN:name [dir=back color=blue penwidth=2]
}
```
你也可以使用負索引指定區間, 甚至正負索引混用, 但都要依循頭端在尾端前面, 例如以下兩者取得的都是同一段區間:
```python
>>> primes[-4:-2]
[2, 3]
>>> primes[-4:3]
[2, 3]
```
由於區間不包含尾端索引位置, 如果要取得包含最後一個物件的區間, 尾端索引就要使用串列結尾的索引 5, 例如:
```python
>>> primes[3:5]
[5, 7]
```
如果省略區間中頭端或是尾端的索引, 就會個別用 0 或是串列結尾的索引取代:
```python
>>> primes[3:] # 取得索引位置 3 開始到結尾的物件
[5, 7]
>>> primes[:3] # 取得串列開頭到索引位置 2 的所有物件
[1, 2, 3]
```
你甚至可以同時省略頭尾端索引, 這樣會取得整個串列。但要注意的是, 取得串列區間永遠都是建立一個新的串列:
```python
>>> primes[:] # 取得整個串列
[1, 2, 3, 5, 7]
>>> new = primes[:]
>>> new is primes # 指定區間取得的永遠是新的串列
False
```
:::info
:memo: 為了行文方便, 通常我們會簡單的說『將物件放入串列中』, 但你只要知道實際上放入的是**綁到物件的名牌**, 這樣在對串列進行某些操作時, 才不會搞不清楚為什麼會是那樣的結果。
:::
## 串列可以放置不同類別的物件
前面的例子為了容易說明, 可能會讓你誤以為串列中只能放置同一類別的物件, 其實串列內可以隨意放置不同類別的物件, 甚至可以放串列, 例如:
```python
>>> objs = [1, [2, 3], "hello"]
>>> objs
[1, [2, 3], 'hello']
```
索引位置 1 就是另外一個串列, 而索引位置 2 的是字串:
```graphviz
digraph object {
var1 [label="<name> objs" shape=record];
obj [label="{0|<f0>-3}|{1|<f1>-2}|{2|<f2>-1}|<f5>3" shape=Mrecord];
obj0 [label="{1|}" shape=Mrecord];
obj1 [label="{0|<f0>-2}|{1|<f1>-1}|2}" shape=Mrecord];
obj2 [label="{hello|}" shape=Mrecord];
obj12 [label="{2|}" shape=Mrecord];
obj13 [label="{3|}" shape=Mrecord];
var1:name -> obj
obj:f0->obj0
obj:f1->obj1
obj:f2->obj2
obj1:f0->obj12
obj1:f1->obj13
}
```
用多層索引就可以取得串列中的串列裡面的物件:
```python
>>> objs[1]
[2, 3]
>>> objs[1][1]
3
```
其中 `objs[1][1]` 的意思就是先用 `objs[1]` 取出索引位置 1 的串列, 再取出這個串列中索引位置 1 的物件, 所以會得到 3。
```graphviz
digraph object {
var1 [label="<name> objs" shape=record];
obj [label="{0|<f0>-3}|{1|<f1>-2}|{2|<f2>-1}|<f5>3" shape=Mrecord];
obj0 [label="{1|}" shape=Mrecord];
obj1 [label="{0|<f0>-2}|{1|<f1>-1}|2}" shape=Mrecord];
obj2 [label="{hello|}" shape=Mrecord];
obj12 [label="{2|}" shape=Mrecord];
obj13 [label="{3|}" shape=Mrecord];
var1:name -> obj
obj:f0->obj0
obj:f1->obj1 [color=blue penwidth=2]
obj:f2->obj2
obj1:f0->obj12
obj1:f1->obj13 [color=blue penwidth=2]
}
```
:::info
:memo: 像串列這種可以放置多個物件的資料類別, 統稱為『**容器 (container)**』, 稍後還會介紹『**字典 (dictionary)**』、『**元組 (tuple)**』等不同類型的容器。
:::
## 增刪以及修改串列內容
前面提到過串列就像是名牌的活動組合櫃, 因此還可以隨時調整名牌數量或是抽換名牌。也就是說, 串列建立後, 可以隨時增、刪物件, 你可以透過串列提供的不同操作方法完成不同工作:
```python
>>> primes
[1, 2, 3, 5, 7]
>>> primes.append(13) # 添加新物件到串列尾端
>>> primes
[1, 2, 3, 5, 7, 13]
>>> primes.insert(5, 11) # 在索引位置 5 前面加入新的物件 11
>>> primes
[1, 2, 3, 5, 7, 11, 13]
>>> primes.append(14)
>>> primes
[1, 2, 3, 5, 7, 11, 13, 14]
>>> primes.pop() # 傳回並移除串列尾端的物件
14
>>> primes
[1, 2, 3, 5, 7, 11, 13]
>>> primes.pop(4) # 傳回並移除索引位置 4 的物件
7
>>> primes
[1, 2, 3, 5, 11, 13]
```
:::warning
:warning: 這裡講的增刪物件, 實際上都是在串列中增刪名牌而已。例如:
```python=
>>> a = [2 , 3]
>>> b = [a] # b 中只有 1 個名牌, 綁到串列 [2, 3] 上
>>> b.pop() # 移除 b 中唯一的名牌, 並傳回它所繫結的物件
[2, 3]
>>> a # 物件還在
[2, 3]
>>> b # b 串列已經空了
[]
```
第 2 列執行後, 共有 a 以及 b[0] 兩個名牌綁到 [2, 3], 第 3 列的 `pop()` 是把 b[0] 名牌刪除, 但 a 這個名牌仍然綁在 [2, 3] 上, 因此查詢 `a` 仍可得到 `[2, 3]`, 但查詢 `b` 時, 串列中已經空無一物了。
:::
如果想要修改串列的內容, 可以直接使用索引抽換名牌, 例如:
```python=
>>> primes = [1, 2, 3, 4]
>>> primes[3] = 5
>>> primes
[1, 2, 3, 5]
```
第 2 列的意思就是抽掉串列中原本綁到 4 的名牌, 在原處放入綁到 5 的名牌。
你甚至可以把串列的某個區間替換成另一個串列的內容, 例如:
```python=
>>> primes[1:3] = [7]
>>> primes
[1, 7, 5]
```
第 1 列的意思就是抽掉 `[1:3]` 這個區間的名牌, 替換成 `[7]` 這個串列內的所有名牌, 所以原本的 `[2, 3]` 換成 `[7]`, 因此串列中 2 與 3 兩個物件不見了, 變成只有 7 這個物件。以下程式可以把它們再換回來:
```python=
>>> primes[1:2] = [2, 3]
>>> primes
[1, 2, 3, 5]
```
## 小心修改串列的副作用
當串列中含有串列時, 要特別小心透過各種方式取得串列中的串列後, 修改所取得串列的內容會影響原本的串列內容, 例如:
```python=
>>> ori = [1, [2, 3], 4]
>>> sec = ori[1] # 取得 [2, 3] 串列
>>> sec
[2, 3]
>>> sec[0] = 999 # 將 [2, 3] 中的 2 換成 999
>>> sec
[999, 3] # 確認已經置換
>>> ori
[1, [999, 3], 4] # 原先串列的內容也被換掉了
```
這是因為 `sec` 和 `ori[1]` 是綁到同一個串列, 所以不論是透過 `sec` 或是 `ori[i]` 修改的都是同一個串列的內容:
```graphviz
digraph object {
var1 [label="<name> ori" shape=record];
var2 [label="<name> sec" shape=record color=Blue];
obj [label="{0|<f0>-3}|{1|<f1>-2}|{2|<f2>-1}|<f5>3" shape=Mrecord];
obj0 [label="{1|}" shape=Mrecord];
obj1 [label="{0|<f0>-2}|{1|<f1>-1}|<f2>2}" shape=Mrecord color=Blue penwidth=2];
obj2 [label="{4|}" shape=Mrecord];
obj12 [label="{2|}" shape=Mrecord];
obj13 [label="{3|}" shape=Mrecord];
var1:name -> obj [color=Blue penwidth=2]
obj1:f2->var2:name [dir=back color=Blue penwidth=2]
obj:f0->obj0
obj:f1->obj1 [color=Blue penwidth=2]
obj:f2->obj2
obj1:f0->obj12
obj1:f1->obj13
}
```
如果沒有注意到, 就會發生透過 `sec` 修改串列, 而使用 `ori[1]` 時卻以為串列還是原本內容的狀況。
:::info
:memo: 為了避免上述的副作用, 你可以使用串列提供的 `copy()` 方法, 或是使用 `copy` 模組提供的函式來複製一個相同內容的新串列, 我們會在使用到時再說明。
:::
## 字串 (String)
字串是用來表達一串文字的物件, 你可以將它當成是特殊的串列, 但串列中的各個名牌都只能繫結到**單一字元**, 而且字串建立後就**不能修改個別名牌所繫結的物件**。
建立字串的方式可以使用成對的英文單引號 `'` `'` 或英文雙引號 `"` `"` 包夾一串文字, 例如:
```python=
>>> a = "hello, world"
>>> a
'hello, world'
>>> b = 'hello, world'
>>> b
'hello, world'
```
字串也可以使用索引來操作, 不過由於 Python 中沒有單一字元的資料類別, 所以不論是用單一索引位置取出字元, 或者是用索引區間取出子字串, 傳回的都是字串:
```python=
>>> a[0]
'h'
>>> a[0:1]
'h'
>>> a[7:]
'world'
>>> a[0] = 'N'
Traceback (most recent call last):
File "<pyshell#167>", line 1, in <module>
a[0] = 'N'
TypeError: 'str' object does not support item assignment
```
前面提到過, 字串建立後就不能更改內容, 因此最後的 `a[0] = 'N'` 會出現錯誤, 錯誤訊息就告訴你字串物件並不提供單一資料項目的指派運算。
如果字串的內容要跨越多列, 那麼可以使用成對的 3 個單引號或雙引號, 例如:
```python=
>>> a = """Hello
world,
my dear.
"""
>>> a
'Hello\nworld,\n my dear. \n'
>>> print(a)
Hello
world,
my dear.
>>>
```
Python 會把成對的 3 個引號之間的內容一字不漏的變成字串的內容, 在交談模式下, 查詢字串物件的內容時會以特殊符號 `\n` 表示換列, 因此上述範例中第 6 列看到的就有 3 個換列的符號, 如果你想讓字串以原貌顯示, 可以改用內建函式 `print`, 上述範例中第 8~11 就是顯示的結果。
字串提供有許多針對文字處理的方法, 例如若想除去字串尾端多餘的空白, 可以叫用 `rstrip`:
```python
>>> a = "hello "
>>> a
'hello '
>>> a.rstrip()
'hello'
```
要注意的是, 字串建立後是不能更改內容的, 所以像是 `rstrip` 傳回的是除去原字串尾端空白後的新字串, 而不是修改原始字串的內容。
:::info
:memo: 有關字串的個別方法, 可以參考 [Python 說明文件](https://docs.python.org/3.4/library/stdtypes.html#string-methods)
:::
## 元組 (tuple)
Python 中另外有一種類別稱為**元組 (tuple)**, 它和串列幾乎一樣, 唯一的差別就是串列是**可調整數量**並**抽換名牌**的活動組合櫃, 但是元組是**固定式**的整理櫃, 無法調整名牌數量, 也不能抽換名牌, 也就是元組建立之後, 就**不能再增刪或是抽換**名牌了。建立元組的方式是以逗號隔開要放入元組的個別物件, 例如:
```python
>>> primes = 1, 2, 3, 5
>>> primes
(1, 2, 3, 5)
>>> type(primes)
<class 'tuple'>
```
交談模式下會以成對的小括號來顯示元組, 在建立元組的時候, 你也可以加上這樣成對的小括號, 例如:
```python
>>> primes = (1, 2, 3, 5)
>>> primes
(1, 2, 3, 5)
```
這對小括號雖然可以不加, 但是在某些特別的情況下, 如果沒有加可能會讓 Python 無法或是錯認程式, 那就非加不可了。舉例來說, 如果遇到要建立不含任何物件的元組時, 就必須用一對小括號來表示:
```python
>>> empty = ()
>>> empty
()
>>> type(empty)
<class 'tuple'>
>>> empty=
SyntaxError: invalid syntax
```
如果只寫 `empty=`, Python 會認為這是錯誤的寫法, 告訴你這樣不合語法。
另外, 如果你要建立的是只有一個物件的元組, 就必須在該物件之後加上逗號, 明確表示這是一個只有單一物件的元組:
```python
>>> single = 20,
>>> single
(20,)
>>> type(single)
<class 'tuple'>
>>> single = 20
>>> single
20
>>> type(single)
<class 'int'>
```
上述範例中, 如果在指派名稱時少了最後的逗號, 就變成幫等號右邊的物件指派名稱, 而不是建立元組之後才指派名稱了。要特別注意的是, 即使加上小括號, 仍然不可省略逗號, 否則 Python 一樣會誤以為是要為等號右邊小括號內的物件指定名稱:
```python=
>>> b = (20)
>>> b
20
>>> b = (20,)
>>> b
(20,)
```
另外, 前面有提到過元組建立後, 就不能增刪或是抽換名牌, 因此以下的操作會出現錯誤:
```python=
>>> primes = (1, [2, 3], 5)
>>> primes[1] = [7, 11]
Traceback (most recent call last):
File "<pyshell#197>", line 1, in <module>
primes[1] = [7, 11]
TypeError: 'tuple' object does not support item assignment
```
這裡因為要抽換索引位置 1 的名牌, 所以 Python 回報錯誤, 告訴你元組並不能抽換名牌。不過由於索引位置 1 的物件是串列, 串列本身可以修改內容, 所以以下的範例就在該串列中增添額外的 2 個物件:
```python
>>> primes[1][2:2] = [7, 11] # 指派 [7, 11] 串列到尾端區間
>>> primes
(1, [2, 3, 7, 11], 5) # 元組中的串列內容改變了
```
瞭解以上這些內容, 我們會在下一章整合所學, 撰寫可以顯示檔案清單的程式, 類似 Windows 字元模式下的 `dir` 指令, 或是 Linux 下的 `ls` 指令。