Python List 與 Array 的差異完整解析
深入理解兩種資料結構的特性與使用時機
目錄
Python List 與 Array 的差異完整解析
前言
在 Python 程式設計中,List 和 Array 是兩個容易混淆的概念。很多初學者會認為它們是相同的東西,但實際上它們有著本質上的差異。本文將深入探討這兩種資料結構的特性、使用場景和效能差異。
快速對照表
特性 | List | Array |
---|---|---|
資料類型 | 可混合不同類型 | 必須相同類型 |
匯入需求 | Python 內建 | 需要匯入模組 |
記憶體效率 | 較低 | 較高 |
運算速度 | 較慢 | 較快 |
功能豐富度 | 豐富 | 基本 |
使用難易度 | 簡單 | 稍複雜 |
什麼是 Python List?
定義與特性
List 是 Python 內建的資料結構,可以儲存任意類型的元素序列。它是 Python 中最常用的資料結構之一。
# List 可以包含不同類型的元素
mixed_list = [1, "hello", 3.14, True, [1, 2, 3], {"key": "value"}]
print(f"混合類型 List: {mixed_list}")
print(f"類型: {type(mixed_list)}")
# List 是可變的(mutable)
numbers = [1, 2, 3, 4, 5]
numbers[0] = 10 # 修改元素
numbers.append(6) # 新增元素
print(f"修改後: {numbers}")
List 的內部實作
Python List 在底層是使用動態陣列實作的:
import sys
# 觀察 List 的記憶體使用
list_sizes = []
list_obj = []
for i in range(20):
list_obj.append(i)
size = sys.getsizeof(list_obj)
list_sizes.append((len(list_obj), size))
# 顯示結果
print("元素數量 | 記憶體大小(bytes)")
print("-" * 30)
for length, size in list_sizes[:10]: # 顯示前10筆
print(f"{length:8} | {size:10}")
List 的常用操作
# 1. 建立 List
# 空 List
empty_list = []
empty_list2 = list()
# 從其他可迭代物件建立
from_string = list("hello")
from_range = list(range(5))
from_tuple = list((1, 2, 3))
print(f"從字串: {from_string}")
print(f"從 range: {from_range}")
print(f"從 tuple: {from_tuple}")
# 2. List Comprehension
squares = [x**2 for x in range(10)]
even_squares = [x**2 for x in range(10) if x % 2 == 0]
matrix = [[i+j for j in range(3)] for i in range(3)]
print(f"\n平方數: {squares}")
print(f"偶數平方: {even_squares}")
print(f"矩陣: {matrix}")
# 3. 常用方法
fruits = ["apple", "banana", "orange"]
# 新增元素
fruits.append("grape") # 尾端新增
fruits.insert(1, "mango") # 指定位置插入
fruits.extend(["kiwi", "pear"]) # 擴展多個元素
# 移除元素
removed = fruits.pop() # 移除並回傳最後一個
fruits.remove("banana") # 移除指定值
# del fruits[0] # 刪除指定索引
# 搜尋與排序
print(f"'mango' 的索引: {fruits.index('mango')}")
print(f"'apple' 出現次數: {fruits.count('apple')}")
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort() # 原地排序
print(f"排序後: {numbers}")
sorted_numbers = sorted([3, 1, 4, 1, 5, 9, 2, 6]) # 回傳新的已排序 List
print(f"sorted() 結果: {sorted_numbers}")
什麼是 Python Array?
Python 中的 Array 主要有兩種:標準函式庫的 array
模組和第三方套件 NumPy
的 array。
1. 標準函式庫的 array
import array
# 建立 array 需要指定類型碼
# 'i' 代表 signed int
int_array = array.array('i', [1, 2, 3, 4, 5])
print(f"整數 array: {int_array}")
print(f"類型: {type(int_array)}")
# 常見的類型碼
# 'b': signed char (int, 1 byte)
# 'B': unsigned char (int, 1 byte)
# 'h': signed short (int, 2 bytes)
# 'H': unsigned short (int, 2 bytes)
# 'i': signed int (int, 4 bytes)
# 'I': unsigned int (int, 4 bytes)
# 'f': float (float, 4 bytes)
# 'd': double (float, 8 bytes)
# 不同類型的 array
float_array = array.array('f', [1.1, 2.2, 3.3])
char_array = array.array('u', 'hello') # Unicode 字元
print(f"\n浮點數 array: {float_array}")
print(f"字元 array: {char_array}")
# array 只能存放相同類型
try:
mixed_array = array.array('i', [1, 2, "3"]) # 這會出錯
except TypeError as e:
print(f"\n錯誤: {e}")
# array 的操作方法
numbers = array.array('i', [1, 2, 3])
numbers.append(4) # 新增元素
numbers.extend([5, 6, 7]) # 擴展
numbers.insert(0, 0) # 插入
print(f"\n操作後: {numbers}")
# 型別資訊
print(f"型別碼: {numbers.typecode}")
print(f"每個元素大小: {numbers.itemsize} bytes")
2. NumPy Array
NumPy 是 Python 科學計算的核心套件,其 array 功能更加強大:
import numpy as np
# 建立 NumPy array
np_array = np.array([1, 2, 3, 4, 5])
print(f"NumPy array: {np_array}")
print(f"類型: {type(np_array)}")
print(f"資料型態: {np_array.dtype}")
print(f"形狀: {np_array.shape}")
# 建立不同維度的 array
# 1D array
arr_1d = np.array([1, 2, 3, 4])
# 2D array (矩陣)
arr_2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 3D array
arr_3d = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
print(f"\n1D array: {arr_1d}")
print(f"2D array:\n{arr_2d}")
print(f"3D array:\n{arr_3d}")
# 使用內建函數建立
zeros = np.zeros((3, 3)) # 全零矩陣
ones = np.ones((2, 4)) # 全一矩陣
eye = np.eye(3) # 單位矩陣
random = np.random.rand(3, 3) # 隨機矩陣
print(f"\n零矩陣:\n{zeros}")
print(f"\n單位矩陣:\n{eye}")
# 指定資料型態
int_array = np.array([1.1, 2.2, 3.3], dtype=int)
float_array = np.array([1, 2, 3], dtype=float)
print(f"\n強制轉換為整數: {int_array}")
print(f"強制轉換為浮點數: {float_array}")
記憶體使用比較
import sys
import numpy as np
import array
# 測試資料:10000 個整數
size = 10000
data = list(range(size))
# Python List
py_list = data
list_size = sys.getsizeof(py_list)
# array 模組
arr = array.array('i', data)
array_size = sys.getsizeof(arr)
# NumPy array
np_arr = np.array(data)
numpy_size = np_arr.nbytes
print(f"儲存 {size} 個整數的記憶體使用:")
print(f"Python List: {list_size:,} bytes")
print(f"array.array: {array_size:,} bytes")
print(f"NumPy array: {numpy_size:,} bytes")
print(f"\n記憶體效率:")
print(f"array 相比 List 節省: {(1 - array_size/list_size)*100:.1f}%")
print(f"NumPy 相比 List 節省: {(1 - numpy_size/list_size)*100:.1f}%")
# 詳細分析單個元素的記憶體使用
print(f"\n單個元素的記憶體開銷:")
print(f"Python int object: {sys.getsizeof(1)} bytes")
print(f"array element: {arr.itemsize} bytes")
print(f"NumPy element: {np_arr.itemsize} bytes")
效能比較
1. 基本運算效能
import time
import numpy as np
import array
# 準備測試資料
size = 1000000
py_list = list(range(size))
arr = array.array('i', range(size))
np_arr = np.arange(size)
# 測試函數
def time_operation(func, name):
start = time.time()
result = func()
end = time.time()
print(f"{name}: {(end - start)*1000:.2f} ms")
return result
print("=== 求和運算 ===")
time_operation(lambda: sum(py_list), "Python List (sum)")
time_operation(lambda: sum(arr), "array.array (sum)")
time_operation(lambda: np.sum(np_arr), "NumPy array (np.sum)")
print("\n=== 元素平方運算 ===")
time_operation(lambda: [x**2 for x in py_list[:10000]], "List comprehension")
time_operation(lambda: np_arr[:10000]**2, "NumPy vectorized")
print("\n=== 元素相乘 ===")
def list_multiply():
return [a * b for a, b in zip(py_list[:10000], py_list[:10000])]
def numpy_multiply():
return np_arr[:10000] * np_arr[:10000]
time_operation(list_multiply, "List zip multiply")
time_operation(numpy_multiply, "NumPy multiply")
2. 索引存取效能
import random
# 隨機存取測試
indices = [random.randint(0, size-1) for _ in range(10000)]
print("\n=== 隨機存取 10000 次 ===")
def list_access():
return [py_list[i] for i in indices]
def array_access():
return [arr[i] for i in indices]
def numpy_access():
return np_arr[indices] # NumPy 支援向量化索引
time_operation(list_access, "Python List")
time_operation(array_access, "array.array")
time_operation(numpy_access, "NumPy array")
實際應用場景
1. 何時使用 List
# 場景 1: 儲存異質資料
user_data = [
"John Doe", # 姓名 (str)
25, # 年齡 (int)
175.5, # 身高 (float)
True, # 是否活躍 (bool)
["Python", "Java"], # 技能 (list)
{"city": "Taipei"} # 其他資訊 (dict)
]
# 場景 2: 動態資料收集
collected_data = []
for i in range(10):
# 模擬收集不同類型的資料
if i % 2 == 0:
collected_data.append(i)
else:
collected_data.append(f"item_{i}")
print(f"收集的資料: {collected_data}")
# 場景 3: 資料處理管線
def process_pipeline(data):
# 可以存放不同階段的處理結果
results = []
results.append(("原始資料", data))
results.append(("過濾後", [x for x in data if x > 0]))
results.append(("排序後", sorted(data)))
return results
pipeline_results = process_pipeline([3, -1, 4, 1, -5, 9])
for stage, result in pipeline_results:
print(f"{stage}: {result}")
2. 何時使用 Array
# 場景 1: 大量數值運算
import numpy as np
# 影像處理(影像是數值矩陣)
image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
# 調整亮度
brightened = np.clip(image + 50, 0, 255)
# 場景 2: 科學計算
# 計算統計資料
data = np.random.normal(100, 15, 10000) # 平均100, 標準差15
print(f"\n統計資料:")
print(f"平均值: {np.mean(data):.2f}")
print(f"標準差: {np.std(data):.2f}")
print(f"最小值: {np.min(data):.2f}")
print(f"最大值: {np.max(data):.2f}")
# 場景 3: 矩陣運算
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(f"\n矩陣運算:")
print(f"A + B =\n{A + B}")
print(f"A × B =\n{np.dot(A, B)}")
print(f"A 的轉置 =\n{A.T}")
# 場景 4: 訊號處理
# 產生正弦波
sample_rate = 1000
duration = 1
t = np.linspace(0, duration, sample_rate)
frequency = 5
signal = np.sin(2 * np.pi * frequency * t)
# 加入雜訊
noise = np.random.normal(0, 0.1, len(signal))
noisy_signal = signal + noise
進階比較:List vs NumPy Array
1. 記憶體配置方式
# List: 存放物件的參照
list_example = [1, 2, 3]
# 實際上是: [指向int(1)的指標, 指向int(2)的指標, 指向int(3)的指標]
# NumPy: 連續的記憶體區塊
np_example = np.array([1, 2, 3])
# 實際上是: [1][2][3] (連續存放的數值)
# 視覺化差異
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# List 結構圖
ax1.text(0.5, 0.9, 'Python List', ha='center', va='center', fontsize=14, weight='bold')
ax1.text(0.5, 0.7, 'List Object', ha='center', va='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue"))
# 畫箭頭指向各個物件
for i, val in enumerate([1, 2, 3]):
x = 0.2 + i * 0.3
ax1.arrow(0.5, 0.6, x-0.5, -0.25, head_width=0.03, head_length=0.02, fc='black', ec='black')
ax1.text(x, 0.2, f'int({val})', ha='center', va='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightyellow"))
# NumPy 結構圖
ax2.text(0.5, 0.9, 'NumPy Array', ha='center', va='center', fontsize=14, weight='bold')
ax2.text(0.5, 0.7, 'Array Header', ha='center', va='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightgreen"))
ax2.text(0.5, 0.4, '[1][2][3]', ha='center', va='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightcoral"))
ax2.text(0.5, 0.2, '連續記憶體', ha='center', va='center', fontsize=12)
for ax in [ax1, ax2]:
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
plt.tight_layout()
# plt.show() # 如果要顯示圖形
2. 向量化運算
# List 需要迴圈
list1 = [1, 2, 3, 4, 5]
list2 = [6, 7, 8, 9, 10]
# 相加
list_sum = []
for i in range(len(list1)):
list_sum.append(list1[i] + list2[i])
print(f"List 相加 (迴圈): {list_sum}")
# 或使用 list comprehension
list_sum2 = [a + b for a, b in zip(list1, list2)]
print(f"List 相加 (comprehension): {list_sum2}")
# NumPy 向量化運算
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([6, 7, 8, 9, 10])
arr_sum = arr1 + arr2 # 直接相加!
print(f"NumPy 相加: {arr_sum}")
# 更複雜的運算
result = (arr1 ** 2 + arr2 ** 2) ** 0.5 # 計算歐幾里得距離
print(f"複雜運算結果: {result}")
3. 廣播(Broadcasting)
# NumPy 的廣播機制
arr = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 數值與陣列運算
print("原始陣列:")
print(arr)
print("\n陣列 + 10:")
print(arr + 10) # 自動將 10 廣播到每個元素
print("\n陣列 * [1, 2, 3]:")
print(arr * [1, 2, 3]) # 自動將 [1,2,3] 廣播到每一行
# List 無法這樣做
try:
list_2d = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = list_2d + 10 # 這會出錯
except TypeError as e:
print(f"\nList 不支援廣播: {e}")
選擇指南
決策流程圖
def choose_data_structure(requirements):
"""幫助選擇適合的資料結構"""
print("=== 資料結構選擇指南 ===\n")
# 問題 1
mixed_types = input("需要儲存不同類型的資料嗎?(y/n): ").lower() == 'y'
if mixed_types:
print("→ 建議使用 Python List")
return "List"
# 問題 2
numerical_ops = input("需要進行大量數值運算嗎?(y/n): ").lower() == 'y'
if numerical_ops:
print("→ 建議使用 NumPy Array")
return "NumPy"
# 問題 3
size = input("資料量大嗎(超過10萬筆)?(y/n): ").lower() == 'y'
if size:
memory_critical = input("記憶體使用是關鍵考量嗎?(y/n): ").lower() == 'y'
if memory_critical:
print("→ 建議使用 array.array 或 NumPy Array")
return "array.array"
# 預設
print("→ 建議使用 Python List (最通用)")
return "List"
# 執行選擇器(取消註解以執行)
# recommendation = choose_data_structure({})
實用建議總結
# 1. 一般程式設計:使用 List
shopping_cart = ["蘋果", "香蕉", "牛奶"]
user_info = ["Alice", 25, "alice@email.com"]
# 2. 數值計算:使用 NumPy
import numpy as np
temperatures = np.array([23.5, 24.1, 22.8, 25.3, 23.9])
average_temp = np.mean(temperatures)
# 3. 記憶體受限但不需要 NumPy 功能:使用 array
import array
sensor_readings = array.array('f', [0.0] * 1000000) # 預分配空間
# 4. 資料分析:使用 NumPy 或 Pandas
data_matrix = np.random.randn(1000, 50) # 1000筆資料,50個特徵
correlation = np.corrcoef(data_matrix.T) # 計算相關係數
常見錯誤與陷阱
1. 類型混淆
# 錯誤:試圖在 array 中混合類型
import array
try:
# 這會失敗
mixed = array.array('i', [1, 2, 3.14])
except TypeError as e:
print(f"錯誤: {e}")
# 正確:使用適當的類型碼
float_arr = array.array('f', [1, 2, 3.14]) # 使用 'f' 來存浮點數
print(f"正確: {float_arr}")
2. 效能迷思
# 迷思:NumPy 總是比較快
# 事實:對於小資料或簡單操作,List 可能更快
import time
small_list = [1, 2, 3, 4, 5]
small_array = np.array([1, 2, 3, 4, 5])
# 小資料的簡單操作
start = time.time()
for _ in range(100000):
_ = small_list[2]
list_time = time.time() - start
start = time.time()
for _ in range(100000):
_ = small_array[2]
array_time = time.time() - start
print(f"存取單一元素 (100,000次):")
print(f"List: {list_time:.4f} 秒")
print(f"NumPy: {array_time:.4f} 秒")
3. 記憶體管理
# List 的記憶體增長策略
import sys
lst = []
sizes = []
for i in range(20):
lst.append(i)
sizes.append(sys.getsizeof(lst))
# 觀察記憶體增長模式
growth_points = []
for i in range(1, len(sizes)):
if sizes[i] > sizes[i-1]:
growth_points.append((i, sizes[i] - sizes[i-1]))
print("List 記憶體增長點:")
for index, growth in growth_points:
print(f"在第 {index} 個元素時增長 {growth} bytes")
總結
核心差異回顧
類型限制
- List:無限制,可混合任意類型
- Array:嚴格限制,只能相同類型
記憶體效率
- List:較低(存放物件參照)
- Array:較高(直接存放數值)
運算效能
- List:需要迴圈,較慢
- NumPy Array:向量化運算,極快
使用便利性
- List:內建、簡單、功能豐富
- Array:需匯入、功能較少
選擇建議
- 預設選擇 List:除非有特定理由
- 大量數值運算選 NumPy:科學計算、資料分析、機器學習
- 記憶體關鍵選 array.array:嵌入式系統、大量簡單數值
- 混合使用:根據不同階段的需求轉換
記住:「沒有最好的資料結構,只有最適合的資料結構」。理解它們的差異,才能在正確的場景使用正確的工具!