人工智能之數據分析 numpy
第十章 副本視圖
(文章目錄)
前言
在 NumPy 中,副本(copy) 和 視圖(view) 是理解數組內存管理、性能優化和避免意外修改的關鍵概念。它們決定了當你對一個數組進行切片、變形或賦值操作時,**是否創建了新的數據副本,還是僅僅創建了一個指向原數據的新“窗口”**。
下面從原理、區別、判斷方法到實戰示例,詳細解析:
一、核心概念
| 概念 | 含義 | 是否共享數據 | 修改影響 |
|---|---|---|---|
| 副本(Copy) | 完全獨立的新數組,擁有自己的內存空間 | ❌ 不共享 | 修改副本不影響原數組 |
| 視圖(View) | 原數組的一個“窗口”或“別名”,不復制數據 | ✅ 共享 | 修改視圖會改變原數組 |
💡 關鍵點:NumPy 默認儘可能返回 視圖(為了節省內存和提升速度),只有在必要時才返回副本。
二、如何判斷是副本還是視圖?
使用數組的 .base 屬性:
- 如果
arr.base is None→ 這是一個副本(或原始數組) - 如果
arr.base is not None→ 這是一個視圖,base指向原始數組
import numpy as np
a = np.array([1, 2, 3, 4])
b = a.view() # 顯式創建視圖
c = a.copy() # 顯式創建副本
d = a[1:3] # 切片(通常返回視圖)
print(b.base is a) # True → 視圖
print(c.base is a) # False → 副本(c.base is None)
print(d.base is a) # True → 視圖(切片是視圖!)
三、常見操作:返回視圖 vs 副本
✅ 返回 視圖 的操作(共享數據)
| 操作 | 示例 | 説明 |
|---|---|---|
| 切片(slicing) | a[1:4] |
最常見! |
np.view() |
a.view() |
顯式創建視圖 |
reshape()(當可能時) |
a.reshape(2,2) |
如果不改變數據佈局,返回視圖 |
轉置 T |
a.T |
對高維數組通常是視圖 |
a = np.array([[1, 2], [3, 4]])
b = a.reshape(4,) # 視圖(連續內存)
b[0] = 99
print(a) # [[99, 2], [3, 4]] → 原數組被修改!
⚠️ 注意:
reshape()不一定總是視圖!如果無法在不復制數據的情況下 reshape(如非連續數組),NumPy 會自動返回副本。
✅ 返回 副本 的操作(獨立數據)
| 操作 | 示例 | 説明 |
|---|---|---|
np.copy() |
a.copy() |
顯式深拷貝 |
| 花式索引(fancy indexing) | a[[0, 2, 1]] |
總是副本 |
| 布爾索引 | a[a > 2] |
總是副本 |
flatten() |
a.flatten() |
總是返回副本 |
ravel()(有時) |
a.ravel() |
儘量返回視圖,不行則副本(與 flatten 不同) |
a = np.array([1, 2, 3, 4])
# 花式索引 → 副本
b = a[[0, 2]]
b[0] = 99
print(a) # [1, 2, 3, 4] → 未變
# flatten → 副本
c = a.flatten()
c[0] = 88
print(a) # 仍為 [1, 2, 3, 4]
四、reshape() 和 ravel() 的細節對比
| 方法 | 是否修改原數組 | 返回類型 | 內存行為 |
|---|---|---|---|
arr.reshape(...) |
❌ | 新數組 | 儘量視圖,否則副本 |
arr.resize(...) |
✅ | 無返回(in-place) | 直接修改原數組形狀 |
arr.ravel() |
❌ | 一維數組 | 儘量視圖 |
arr.flatten() |
❌ | 一維數組 | 總是副本 |
a = np.array([[1, 2], [3, 4]])
# ravel() → 視圖(因為 a 是連續的)
b = a.ravel()
b[0] = 99
print(a) # [[99, 2], [3, 4]]
# flatten() → 副本
c = a.flatten()
c[0] = 88
print(a) # 仍是 [[99, 2], [3, 4]]
五、實戰陷阱:意外修改原數組
❌ 錯誤示例:以為切片是副本
data = np.array([10, 20, 30, 40])
subset = data[1:3] # 這是視圖!
subset[0] = 999 # 你以為只改 subset?
print(data) # [10, 999, 30, 40] → 原數組被改了!
✅ 正確做法:明確需要副本時用 .copy()
data = np.array([10, 20, 30, 40])
subset = data[1:3].copy() # 顯式創建副本
subset[0] = 999
print(data) # [10, 20, 30, 40] → 安全!
六、何時用視圖?何時用副本?
| 場景 | 推薦 |
|---|---|
| 只讀訪問子數據(如分析某段信號) | 用視圖(高效) |
| 需要修改子數據但不想影響原數組 | 用 .copy() |
| 函數內部處理數組,不確定是否會被修改 | 默認 .copy() 更安全 |
| 內存受限,且確定不會修改 | 用視圖節省內存 |
七、總結速查表
| 操作 | 返回 | 是否共享數據 | 安全修改? |
|---|---|---|---|
a[:] |
視圖 | ✅ | ❌(會影響 a) |
a[1:3] |
視圖 | ✅ | ❌ |
a[[1,2]] |
副本 | ❌ | ✅ |
a[a>0] |
副本 | ❌ | ✅ |
a.reshape(...) |
視圖(儘量) | 可能 | 謹慎 |
a.copy() |
副本 | ❌ | ✅ |
a.view() |
視圖 | ✅ | ❌ |
a.flatten() |
副本 | ❌ | ✅ |
a.ravel() |
視圖(儘量) | 可能 | 謹慎 |
八、最佳實踐建議
- 永遠不要假設切片是副本 → 如需獨立數據,顯式調用
.copy() - 在函數參數中接收數組時,若要修改,先
.copy()避免副作用 - 使用
.base屬性調試內存關係 - 處理大型數組時,優先使用視圖以節省內存,但要小心寫操作
後續
本文主要講述了numpy數組副本和視圖。python過渡項目部分代碼已經上傳至gitee,後續會逐步更新,主要受時間原因限制,當然自己也可以克隆到本地學習拓展。
資料關注
公眾號:咚咚王 gitee:https://gitee.com/wy18585051844/ai_learning
《Python編程:從入門到實踐》 《利用Python進行數據分析》 《算法導論中文第三版》 《概率論與數理統計(第四版) (盛驟) 》 《程序員的數學》 《線性代數應該這樣學第3版》 《微積分和數學分析引論》 《(西瓜書)周志華-機器學習》 《TensorFlow機器學習實戰指南》 《Sklearn與TensorFlow機器學習實用指南》 《模式識別(第四版)》 《深度學習 deep learning》伊恩·古德費洛著 花書 《Python深度學習第二版(中文版)【純文本】 (登封大數據 (Francois Choliet)) (Z-Library)》 《深入淺出神經網絡與深度學習+(邁克爾·尼爾森(Michael+Nielsen)》 《自然語言處理綜論 第2版》 《Natural-Language-Processing-with-PyTorch》 《計算機視覺-算法與應用(中文版)》 《Learning OpenCV 4》 《AIGC:智能創作時代》杜雨+&+張孜銘 《AIGC原理與實踐:零基礎學大語言模型、擴散模型和多模態模型》 《從零構建大語言模型(中文版)》 《實戰AI大模型》 《AI 3.0》