清單

清單 在 Python

96 個練習

關於 清單

list 是一種可變的集合,其中的項目會依_序列_排列。和大多數集合一樣(請參考內建的 tuple、dict 和 set),陣列可以保存對任何一種(或多種)資料型態的參照,包括其他陣列。如同任何序列,可以從左邊用 0-based index 編號存取項目,也可以從右邊用 -1-based index 存取。陣列可以透過切片表示法或 <list>.copy() 整份複製或部分複製。

陣列同時支援一般與可變序列操作,例如 min()/max()、<list>.index()、.append() 和 .reverse()。可以使用 for item in <list> 這樣的結構來疊代陣列元素。當同時需要元素索引和元素值時,可以改用 for index, item in enumerate(<list>)。

陣列是以動態陣列實作的,類似 Java 的 Arraylist 型別,最常用來存放一群型態相近的資料(字串、數字、集合等),其長度未知(項目的數量可以任意增加或減少)。

存取元素、用 in 檢查是否包含某個項目,或把項目附加到陣列的「右端」,都非常有效率。在開頭插入(附加到「左端」)或插入到陣列中間,效率就低了許多,因為這些操作需要搬移元素來維持它們的順序。如果想找一個類似、能在兩端有效率地進行 appends/pops 又能節省記憶體的資料結構,可以參考 collections.deque,它在兩個方向上的效能大致都是 O(1)。

由於陣列是可變的,而且可以包含對任意 Python 物件的參照,因此和外觀長度相同的 array.array 或 tuple(不可變)相比,它們佔用的記憶體也更多。儘管如此,陣列仍是極具彈性又實用的資料結構,Python 中許多內建的方法和操作都會產生陣列作為輸出。

建立

list 可以用_字面值_宣告,寫成方括號 [],元素之間以逗號分隔:

>>> no_elements = []

>>> no_elements
[]

>>> one_element = ["Guava"]

>>> one_element
['Guava']

>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]

>>> elements_separated_with_commas
['Parrot', 'Bird', 334782]

為了方便閱讀,當陣列裡有很多元素或巢狀資料結構時,可以改用換行來排列:

>>> lots_of_entries = [
...    "Rose",
...    "Sunflower",
...    "Poppy",
...    "Pansy",
...    "Tulip",
...    "Fuchsia",
...    "Cyclamen",
...    "Lavender"
... ]

>>> lots_of_entries
['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']


# Each data structure is on its own line to help clarify what they are.
>>> nested_data_structures = [
...    {"fish": "gold", "monkey": "brown", "parrot": "grey"},
...    ("fish", "mammal", "bird"),
...    ['water', 'jungle', 'sky']
... ]

>>> nested_data_structures
[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]

list() 建構子可以空著使用,也可以接收一個_可疊代物件_作為引數。建構子會逐一走訪可疊代物件中的元素,並依序加入陣列:

>>> no_elements = list()
>>> no_elements
[]

# The tuple is unpacked and each element is added.
>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))

>>> multiple_elements_from_tuple
['Parrot', 'Bird', 334782]

# The set is unpacked and each element is added.
>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})

>>> multiple_elements_from_set
[2, 3, 5, 7, 11]

把字串或 dict 傳給 list 建構子時,結果可能會出乎意料:

# String elements (Unicode code points) are iterated through and added *individually*.
>>> multiple_elements_string = list("Timbuktu")

>>> multiple_elements_string
['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']

# Unicode separators and positioning code points are also added *individually*.
>>> multiple_code_points_string = list('अभ्यास')

>>> multiple_code_points_string
['अ', 'भ', '्', 'य', 'ा', 'स']

# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
>>> source_data = {"fish": "gold", "monkey": "brown"}
>>> list(source_data)
['fish', 'monkey']

由於 list() 建構子只接受可疊代物件(或什麼都不接受)作為引數,因此不能疊代的物件會引發 TypeError。所以,用字面值的方式建立只有一個項目的陣列要簡單得多。

# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
>>> one_element = list(16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
>>> one_element_from_iterable = list((16,))

>>> one_element_from_iterable
[16]

存取元素

陣列中的項目(以及其他序列型別的元素,例如 str 和 tuple)可以用_括號表示法_來存取。索引可以從 left --> right(從零開始),或從 right --> left(從 -1 開始)。

從左邊索引 ⟹






0
👇🏾
1
👇🏾
2
👇🏾
3
👇🏾
4
👇🏾
5
👇🏾
P y t h o n
👆🏾
-6
👆🏾
-5
👆🏾
-4
👆🏾
-3
👆🏾
-2
👆🏾
-1





⟸ 從右邊索引
>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]

# Oatmeal is at index 0 or index -4.
>>> breakfast_foods[0]
'Oatmeal'

>>> breakfast_foods[-4]
'Oatmeal'

# Eggs are at index -2 or 2
>>> breakfast_foods[-2]
'Eggs'

>>> breakfast_foods[2]
'Eggs'

# Toast is at -1
>>> breakfast_foods[-1]
'Toast'

陣列中的一個區段可以用_切片表示法_(<list>[<start>:<stop>])來存取。_切片_的定義是位置 index 上符合 start <= index < stop 的元素序列。切片會回傳被「切出來」的項目複本,不會修改原始的 list。

切片也可以使用 step 參數(<list>[<start>:<stop>:<step>])來「跳過」或篩選回傳的元素(例如 step 為 2 時,會選取區段中每隔一個的元素):

>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]

# If there is no step parameter, the step is assumed to be 1.
>>> middle_colors = colors[2:6]

>>> middle_colors
['Green', 'Yellow', 'Orange', 'Pink']

# If the start or stop parameters are omitted, the slice will
# start at index zero, and will stop at the end of the list.
>>> primary_colors = colors[::3]

>>> primary_colors
['Red', 'Yellow', 'Blue']

使用陣列

陣列會提供一個迭代器,可以像其他_序列型別_一樣用 for item in <list> 或 for index, item in enumerate(<list>) 來迴圈走訪:

# Make a list, and then loop through it to print out the elements
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for item in colors:
...     print(item)

Orange
Green
Grey
Blue


# Print the same list, but with the indexes of the colors included
>>> colors = ["Orange", "Green", "Grey", "Blue"]
>>> for index, item in enumerate(colors):
...     print(item, ":", index)

Orange : 0
Green : 1
Grey : 2
Blue : 3


# Start with a list of numbers and then loop through and print out their cubes.
>>> numbers_to_cube = [5, 13, 12, 16]
>>> for number in numbers_to_cube:
...     print(number**3)

125
2197
1728
4096

建立一串值的常見做法,是在迴圈中使用 <list>.append():

>>> cubes_to_1000 = []
>>> for number in range(11):
...    cubes_to_1000.append(number**3)

>>> cubes_to_1000
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

陣列也可以用各種技巧合併:

# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]

>>> new_via_concatenate
['George', 5, 'cat', 'Tabby']

# Likewise, using the multiplication operator * is the equivalent of using + n times.
>>> first_group = ["cat", "dog", "elephant"]
>>> multiplied_group = first_group * 3

>>> multiplied_group
['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']

# Another method for combining 2 lists is to use slice assignment or a loop-append.
# This assigns the second list to index 0 in the first list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
>>> first_one[0:0] = second_one

>>> first_one
['George', 5, 'cat', 'Tabby']

# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]

>>> for item in first_one:
...      second_one.append(item)

>>> second_one
['George', 5, 'cat', 'Tabby']

一些注意事項

回想一下,Python 的變數是_標籤_,指向_底層物件_。lists 又多了一層,因為它們是_容器物件_,會為它們收集的項目保存物件的_參照_。如果沒有妥善處理,在操作陣列時可能會引發各種潛在問題。

把陣列指定給多個變數名稱

把 list 物件指定給一個新的變數_名稱_,並不會複製 list 物件或其元素。透過_新_名稱對 list 中的元素所做的任何變更,都會影響到原本的。

透過 list.copy() 或切片製作 shallow_copy,可以避開這層第一級的參照問題。shallow_copy 會建立新的 list 物件,但不會為其中包含的陣列_元素_建立新物件。這種複製通常就足以讓你獨立地從兩個 list 物件新增或移除項目,實際上擁有兩個「分開」的陣列。

>>> actual_names = ["Tony", "Natasha", "Thor", "Bruce"]

# Assigning a new variable name does not make a copy of the container or its data.
>>> same_list = actual_names

#  Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

#  Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"

>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']

>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]

在處理巢狀或相乘的陣列時,這種參照問題會更加嚴重(以下範例出自 2013 年 Ned Batchelder 一篇很棒的部落格文章 Names and values: making a game board):

from pprint import pprint

# This will produce a game grid that is 8x8, pre-populated with zeros.
>>> game_grid = [[0]*8]*8

>>> pprint(game_grid)
[[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]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# This attempt doesn't work because all the rows are referencing the same underlying list object.
>>> pprint(game_grid)
[[0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X'],
 [0, 0, 0, 0, 0, 0, 0, 'X']]

不過在這種情況下,shallow_copy 就足以達到我們想要的行為:

from pprint import pprint

# This loop will safely produce a game grid that is 8x8, pre-populated with zeros
>>> game_grid = []
>>> filled_row = [0] * 8
>>> for row in range(8):
...    game_grid.append(filled_row.copy()) # This is making a new shallow copy of the inner list object each iteration.

>>> pprint(game_grid)
[[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]]

# An attempt to put a "X" in the bottom right corner.
>>> game_grid[7][7] = "X"

# The game grid now works the way we expect it to!
>>> pprint(game_grid)
[[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, 'X']]

如前所述,陣列是_參照_的容器,所以還有第二層潛在的麻煩。如果陣列包含變數、物件或巢狀資料結構,那些第二層的參照不會透過 shallow_copy 或切片被複製。如此一來,修改底層物件會影響_所有_複本,因為每個 list 物件只包含_指向_所存元素的_參照_。

from pprint import pprint

>>> pprint(game_grid)
[[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, 'X']]

# We'd like a new board, so we make a shallow copy.
>>> new_game_grid = game_grid.copy()

# But a shallow copy doesn't copy the contained references or objects.
>>> new_game_grid[0][0] = 'X'

# So changing the items in the copy also changes the originals items.
>>>  pprint(game_grid)
[['X', 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, 'X']]

相關的資料型態

陣列經常被當作_堆疊_和_佇列_使用,不過它們的底層實作會讓在開頭插入和插入中間變得很慢。collections 模組提供了一種 deque 變體,針對兩端的快速 append 和 pop 最佳化,並以雙向連結串列實作。巢狀陣列也常用來模擬小型_矩陣_,不過 Numpy 和 Pandas 這兩個函式庫在有效率的矩陣與表格資料處理上要強大得多。collections 模組也提供 UserList 型別,可以自訂以符合特殊的陣列需求。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 清單

練習已鎖定

再解鎖 5 個練習,就能練習 清單