
1. 為什么說NumPy是Python科學計算的基石如果你剛開始接觸Python數(shù)據(jù)分析、機器學習或者科學計算那么“NumPy”這個名字一定會高頻出現(xiàn)。很多教程會告訴你“先裝NumPy”但很少會深入解釋為什么偏偏是它而不是別的庫成為了這個領域事實上的標準。今天我們不談空洞的概念就從最實際的“手感”和“性能”出發(fā)聊聊NumPy到底解決了什么痛點以及它的核心——ndarray對象是如何從根本上改變我們處理數(shù)據(jù)的方式的。想象一下你用Python原生的列表list存儲100萬個浮點數(shù)然后想給每個數(shù)都加上1。你會怎么寫一個for循環(huán)列表推導式[x1 for x in my_list]代碼寫起來簡單但當你真正跑起來尤其是數(shù)據(jù)量再大幾個數(shù)量級時你會明顯感覺到“慢”。這種慢根源在于Python列表的靈活性列表里的每個元素都是一個完整的Python對象比如一個float對象它們可以類型不同可以嵌套但也因此每個元素都攜帶了類型信息、引用計數(shù)等額外開銷。進行運算時Python解釋器需要逐個檢查、解包、計算、再打包這個動態(tài)過程消耗了大量時間。NumPy的ndarrayN-dimensional arrayN維數(shù)組就是為了終結(jié)這種低效而生的。它要求數(shù)組內(nèi)的所有元素必須是同質(zhì)的通常是數(shù)字類型并且在內(nèi)存中連續(xù)存儲。這就好比把一隊散兵游勇Python列表整編成了一支紀律嚴明、裝備統(tǒng)一的方陣NumPy數(shù)組。計算機的CPU和內(nèi)存對于處理這種整齊劃一的數(shù)據(jù)塊極其高效因為可以利用向量化指令和緩存預取等底層優(yōu)化。同樣給100萬個數(shù)加1NumPy只需要一條語句arr 1這個操作不是在Python層面用循環(huán)實現(xiàn)的而是被編譯成底層的C代碼在連續(xù)的二進制數(shù)據(jù)塊上一次性完成速度可能提升幾十甚至上百倍。所以學習NumPy首要任務就是忘掉列表的思維定勢建立起“數(shù)組計算”的思維模型。它不是Python列表的替代品而是一個專為高性能數(shù)值計算設計的、全新的數(shù)據(jù)結(jié)構(gòu)。接下來我們就從最基礎的數(shù)組創(chuàng)建開始一步步拆解NumPy的核心函數(shù)。2. 數(shù)組的誕生從無到有構(gòu)建你的數(shù)據(jù)容器創(chuàng)建數(shù)組是使用NumPy的第一步。NumPy提供了十幾種創(chuàng)建數(shù)組的函數(shù)看似繁雜但核心邏輯就幾條從已有數(shù)據(jù)轉(zhuǎn)換、按照規(guī)則生成序列、或者用特定值填充。理解這些函數(shù)的區(qū)別和適用場景能讓你在寫代碼時信手拈來。2.1 從“原材料”直接鑄造np.array()與np.asarray()這是最直接、最常用的方法將Python的序列型數(shù)據(jù)列表、元組等轉(zhuǎn)化為NumPy數(shù)組。import numpy as np # 從列表創(chuàng)建一維數(shù)組 list_data [1, 2, 3, 4, 5] arr_from_list np.array(list_data) print(arr_from_list) # 輸出: [1 2 3 4 5] print(type(arr_from_list)) # 輸出: class numpy.ndarray # 從嵌套列表創(chuàng)建二維數(shù)組矩陣 list_2d [[1, 2, 3], [4, 5, 6]] arr_2d np.array(list_2d) print(arr_2d) # 輸出: # [[1 2 3] # [4 5 6]] print(arr_2d.shape) # 輸出: (2, 3) 表示2行3列這里有個關鍵細節(jié)np.array()會默認復制輸入數(shù)據(jù)生成一個新的數(shù)組對象。如果你有一個巨大的列表并且確定后續(xù)不會修改原始列表這個復制操作可能會成為內(nèi)存和性能的瓶頸。此時np.asarray()就派上用場了。original_list [10, 20, 30] arr_a np.array(original_list) arr_b np.asarray(original_list) # 修改原始列表 original_list[0] 999 print(arr_a) # 輸出: [10 20 30] 未受影響因為它是副本 print(arr_b) # 輸出: [10 20 30] 同樣未受影響等等這里有個常見的誤解等等arr_b怎么也沒變不是說asarray不復制嗎這里是一個重要的知識點np.asarray()只在輸入數(shù)據(jù)本身還不是NumPy數(shù)組時且無法直接轉(zhuǎn)換為目標數(shù)據(jù)類型時才會嘗試避免復制。如果輸入是一個Python列表asarray的行為和array幾乎一樣都會創(chuàng)建新的數(shù)組。它的主要優(yōu)勢體現(xiàn)在輸入已經(jīng)是ndarray時。# 更典型的 asarray 使用場景 existing_arr np.array([1.0, 2.0, 3.0]) # 假設我們有一個函數(shù)它要求輸入是數(shù)組但我們不確定調(diào)用者給的是數(shù)組還是列表 def some_function(input_data): arr np.asarray(input_data) # 如果input_data已經(jīng)是數(shù)組這里不會發(fā)生復制 # ... 對arr進行操作 return arr result some_function(existing_arr) # 高效無復制注意np.array()有一個關鍵參數(shù)dtype用于指定數(shù)組的數(shù)據(jù)類型。如果你不指定NumPy會自行推斷有時推斷結(jié)果可能不是你想要的比如從整數(shù)列表推斷出int32但你需要int64。明確指定dtype是好習慣尤其是在涉及跨平臺或精度要求高的計算時。例如np.array([1, 2, 3], dtypenp.float64)。2.2 生成規(guī)則序列np.arange()、np.linspace()與np.logspace()當我們需要一個等間隔的數(shù)字序列時np.arange()是range()函數(shù)的強化版。# 類似 range(start, stop, step) seq1 np.arange(5) # 默認從0開始步長為1不包括5 print(seq1) # [0 1 2 3 4] seq2 np.arange(2, 10, 2) # 從2開始到10不含步長為2 print(seq2) # [2 4 6 8] seq3 np.arange(0, 1, 0.2) # 支持浮點數(shù)步長 print(seq3) # [0. 0.2 0.4 0.6 0.8]np.arange的一個經(jīng)典“坑”在于處理浮點數(shù)步長時的精度問題。由于浮點數(shù)的二進制表示并不精確np.arange(0, 1, 0.1)生成的最后一個值可能是0.9999999999999999而不是1.0這可能導致后續(xù)判斷出錯。因此當需要固定數(shù)量的等分點時更推薦使用np.linspace。np.linspace(start, stop, num)用于在指定的區(qū)間內(nèi)生成等間隔的num個點。它關注的是點的數(shù)量而不是步長。# 在0到1之間包括0和1生成5個等間隔的點 points np.linspace(0, 1, 5) print(points) # [0. 0.25 0.5 0.75 1. ] # 在0到2π之間生成100個點常用于生成正弦函數(shù)采樣點 x np.linspace(0, 2*np.pi, 100) y np.sin(x)np.logspace(start, stop, num)則是生成等比數(shù)列在[10^start, 10^stop]區(qū)間內(nèi)生成num個對數(shù)等間隔的點。這在需要測試跨越多個數(shù)量級的參數(shù)時非常有用比如學習率搜索。# 生成從10^0到10^2之間的5個點 log_points np.logspace(0, 2, 5) print(log_points) # [ 1. 3.16227766 10. 31.6227766 100. ]2.3 創(chuàng)建特殊值數(shù)組np.zeros()、np.ones()、np.full()與np.empty()在初始化數(shù)組尤其是為后續(xù)計算準備“容器”時這些函數(shù)極其高效。np.zeros(shape)創(chuàng)建指定形狀的全0數(shù)組。np.ones(shape)創(chuàng)建指定形狀的全1數(shù)組。np.full(shape, fill_value)創(chuàng)建指定形狀并用fill_value填充的數(shù)組。np.empty(shape)創(chuàng)建指定形狀的數(shù)組但不初始化其值其內(nèi)容是內(nèi)存中的隨機值取決于當時內(nèi)存的狀態(tài)。速度最快但必須立即用有效數(shù)據(jù)覆蓋。# 創(chuàng)建一個3行4列的全零矩陣 zero_matrix np.zeros((3, 4)) print(zero_matrix) # 創(chuàng)建一個長度為5元素全為3.14的數(shù)組 pi_array np.full(5, 3.14) print(pi_array) # [3.14 3.14 3.14 3.14 3.14] # 創(chuàng)建一個2x2的未初始化數(shù)組危險僅用于性能關鍵且立即賦值的場景 uninitialized np.empty((2, 2)) print(uninitialized) # 輸出內(nèi)容不確定可能是任意值實操心得np.empty的速度優(yōu)勢在需要創(chuàng)建超大臨時數(shù)組并立刻進行填充計算的循環(huán)中才能體現(xiàn)。對于日常使用np.zeros或np.full是更安全、更可讀的選擇。永遠不要直接使用np.empty創(chuàng)建的數(shù)組進行計算除非你百分之百確定你在下一步就會覆蓋所有元素。2.4 創(chuàng)建單位矩陣與對角陣np.eye()與np.diag()在線性代數(shù)運算中單位矩陣和對角陣非常常見。np.eye(N, MNone, k0)用于創(chuàng)建單位矩陣對角線為1其余為0。N是行數(shù)M是列數(shù)默認等于N。k參數(shù)控制對角線的位置k0是主對角線k0在主對角線之上k0在主對角線之下。# 3x3單位矩陣 I np.eye(3) print(I) # [[1. 0. 0.] # [0. 1. 0.] # [0. 0. 1.]] # 創(chuàng)建3x4矩陣且第一超對角線k1為1 eye_k np.eye(3, 4, k1) print(eye_k) # [[0. 1. 0. 0.] # [0. 0. 1. 0.] # [0. 0. 0. 1.]]np.diag(v, k0)有兩種用法1如果v是1-D數(shù)組則將其作為對角線元素構(gòu)造一個方陣2如果v是2-D數(shù)組則提取其第k條對角線。# 用法1用一維數(shù)組創(chuàng)建對角陣 diag_arr np.array([1, 2, 3]) D np.diag(diag_arr) print(D) # [[1 0 0] # [0 2 0] # [0 0 3]] # 用法2提取矩陣的對角線 matrix np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) main_diag np.diag(matrix) # k0 print(main_diag) # [1 5 9] super_diag np.diag(matrix, k1) print(super_diag) # [2 6]3. 數(shù)組的“形”與“魂”維度操作與索引切片創(chuàng)建了數(shù)組接下來就要學會如何操縱它。NumPy數(shù)組有兩個核心屬性形狀shape和數(shù)據(jù)類型dtype。形狀決定了數(shù)組的維度和每個維度的大小而數(shù)據(jù)類型決定了數(shù)組中每個元素在內(nèi)存中如何解釋。3.1 重塑與展平reshape()、resize()與ravel()/flatten()reshape(newshape)是改變數(shù)組視圖形狀最常用的方法。它返回一個新視圖數(shù)據(jù)本身不變。前提是新形狀的元素總數(shù)必須與原數(shù)組一致。arr np.arange(12) # 一維數(shù)組12個元素 [0, 1, 2, ..., 11] print(arr.shape) # (12,) # 重塑為3行4列的二維數(shù)組 arr_2d arr.reshape(3, 4) print(arr_2d.shape) # (3, 4) print(arr_2d) # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] # 也可以使用-1來自動推斷某一維的大小 # 將二維數(shù)組重塑為有6列行數(shù)自動計算 arr_reshaped arr_2d.reshape(-1, 6) print(arr_reshaped.shape) # (2, 6)resize(new_shape)與reshape功能類似但有一個關鍵區(qū)別如果新形狀的元素總數(shù)大于原數(shù)組resize會用0填充多余的部分如果小于則會截斷數(shù)據(jù)。更重要的是arr.resize()是原地操作會直接修改原數(shù)組且沒有返回值。而np.resize(arr, new_shape)是一個函數(shù)返回新數(shù)組原數(shù)組不變同樣遵循填充/截斷規(guī)則。arr np.array([1, 2, 3]) arr.resize(5) # 原地修改用0填充 print(arr) # [1 2 3 0 0] arr2 np.array([1, 2, 3, 4, 5]) new_arr np.resize(arr2, (2, 2)) # 函數(shù)返回新數(shù)組截斷數(shù)據(jù) print(new_arr) # [[1 2] # [3 4]]將多維數(shù)組變成一維有兩種方法ravel()和flatten()。ravel()返回數(shù)組的展平視圖如果可能這意味著修改ravel()返回的數(shù)組可能會影響原數(shù)組。flatten()則總是返回原數(shù)組數(shù)據(jù)的一份拷貝修改它不影響原數(shù)組。arr_2d np.array([[1, 2], [3, 4]]) flat_view arr_2d.ravel() flat_copy arr_2d.flatten() flat_view[0] 99 print(arr_2d) # [[99 2] [ 3 4]] 原數(shù)組被修改了 flat_copy[1] 100 print(arr_2d) # [[99 2] [ 3 4]] 原數(shù)組不受影響3.2 索引與切片像操作列表一樣但更強大NumPy的索引切片語法和Python列表非常相似但在多維情況下更加靈活?;舅饕龑τ谝痪S數(shù)組arr[index]對于二維數(shù)組arr[row_index, col_index]。arr np.arange(10, 20) print(arr[3]) # 13 print(arr[-1]) # 19 負索引從末尾開始 arr_2d np.arange(12).reshape(3, 4) print(arr_2d[1, 2]) # 第1行第2列從0開始計數(shù)6切片Slicingstart:stop:step。與列表切片一樣結(jié)果是原數(shù)組的一個視圖。arr np.arange(10) print(arr[2:7]) # [2 3 4 5 6] print(arr[::2]) # [0 2 4 6 8] 步長為2 print(arr[::-1]) # [9 8 7 6 5 4 3 2 1 0] 反轉(zhuǎn)數(shù)組 arr_2d np.arange(12).reshape(3, 4) print(arr_2d[:2, 1:3]) # 前兩行第1到2列不含第3列 # [[1 2] # [5 6]]花式索引Fancy Indexing使用整數(shù)數(shù)組或布爾數(shù)組進行索引。這會產(chǎn)生數(shù)據(jù)的拷貝而不是視圖。arr np.array([10, 20, 30, 40, 50]) indices [0, 2, 4] print(arr[indices]) # [10 30 50] # 布爾索引非常強大的數(shù)據(jù)篩選工具 mask arr 25 print(mask) # [False False True True True] print(arr[mask]) # [30 40 50]對于多維數(shù)組花式索引可以組合使用實現(xiàn)更復雜的選取。arr_2d np.arange(12).reshape(3, 4) # 選取第0行和第2行的所有列 rows [0, 2] print(arr_2d[rows, :]) # 選取第0行和第2行的第1列和第3列 cols [1, 3] print(arr_2d[rows][:, cols]) # 注意這里用了兩次索引第一次是行第二次是列 # 更簡潔的寫法是arr_2d[np.ix_(rows, cols)] np.ix_函數(shù)用于構(gòu)建開放網(wǎng)格 print(arr_2d[np.ix_(rows, cols)]) # [[ 1 3] # [ 9 11]]踩坑實錄切片產(chǎn)生視圖花式索引產(chǎn)生拷貝。這是一個極易出錯的地方。如果你通過切片修改了數(shù)組原數(shù)組也會變。如果你不希望這樣需要顯式調(diào)用.copy()方法。例如sub_arr arr[2:5].copy()。4. 數(shù)組的“骨架”廣播機制與通用函數(shù)NumPy最強大的特性之一就是允許形狀不同的數(shù)組進行數(shù)學運算。這背后的魔法叫做廣播Broadcasting。理解廣播規(guī)則是寫出高效、簡潔NumPy代碼的關鍵。4.1 廣播機制的核心規(guī)則廣播的核心思想是將較小的數(shù)組“廣播”到較大數(shù)組的形狀以便它們具有兼容的形狀進行逐元素運算。規(guī)則可以簡化為兩條維度對齊從最右邊的維度開始比較兩個數(shù)組的形狀。如果兩個維度相等或者其中一個為1或者其中一個數(shù)組在該維度上不存在即維度數(shù)為1則這兩個維度是兼容的。擴展在兼容的維度上大小為1的維度會被“拉伸”以匹配另一個數(shù)組對應維度的大小。如果某個數(shù)組在某個維度上缺失即維度數(shù)少則在該維度前補1然后進行拉伸。聽起來有點抽象看例子就明白了import numpy as np # 例子1標量與數(shù)組運算標量被視為0維數(shù)組 arr np.ones((3, 4)) result arr 5 # 標量5被廣播為形狀(3,4)的全5矩陣 print(result[0,0]) # 6.0 # 例子2向量與矩陣運算 row_vector np.array([1, 2, 3, 4]) # 形狀 (4,) matrix np.ones((3, 4)) # 形狀 (3, 4) # row_vector形狀(4,) - 補1 - (1, 4) - 拉伸 - (3, 4) result matrix row_vector print(result) # [[2. 3. 4. 5.] # [2. 3. 4. 5.] # [2. 3. 4. 5.]] # 例子3列向量與行向量運算 col_vector np.array([[1], [2], [3]]) # 形狀 (3, 1) row_vector np.array([10, 20, 30, 40]) # 形狀 (4,) # col_vector (3,1) 與 row_vector (4,) 比較 # 首先將row_vector補1 - (1, 4) # 然后對齊col_vector的第二個維度是1 row_vector的第一個維度是1都是1兼容。 # 廣播后col_vector拉伸為(3,4) row_vector拉伸為(3,4) result col_vector row_vector print(result) # [[11 21 31 41] # [12 22 32 42] # [13 23 33 43]]不兼容的例子A np.ones((3, 4, 5)) B np.ones((4, 5)) # 比較最右邊A的5 vs B的5 - 兼容 # 比較次右邊A的4 vs B的4 - 兼容 # 比較最左邊A的3 vs B的缺失- B在此維度補1 - 1 vs 3 - 兼容因為1可以拉伸到3 # 所以可以廣播B最終被廣播為(1,4,5) - (3,4,5) C np.ones((4, 3)) # 比較最右邊A的5 vs C的3 - 不相等且都不是1 - 不兼容會報錯。 # result A C # ValueError: operands could not be broadcast together...4.2 通用函數(shù)向量化計算的引擎通用函數(shù)ufunc是NumPy中另一種實現(xiàn)高效運算的機制。它們是能對數(shù)組進行逐元素操作的函數(shù)。NumPy內(nèi)置了大量的ufunc涵蓋數(shù)學運算、三角函數(shù)、邏輯比較等。arr np.array([1.0, 4.0, 9.0, 16.0]) # 數(shù)學運算 print(np.sqrt(arr)) # 開方 [1. 2. 3. 4.] print(np.exp(arr)) # 指數(shù) print(np.log(arr)) # 自然對數(shù) print(np.sin(arr)) # 正弦 # 二元運算 a np.array([1, 2, 3]) b np.array([4, 5, 6]) print(np.add(a, b)) # 加法 [5 7 9] print(np.multiply(a, b)) # 乘法 [4 10 18] print(np.maximum(a, b)) # 逐元素取最大值 [4 5 6]ufunc通常有兩個有用的輸出參數(shù)out和where。out參數(shù)允許你將結(jié)果直接存入一個已存在的數(shù)組避免創(chuàng)建臨時數(shù)組節(jié)省內(nèi)存。result np.empty_like(a) np.add(a, b, outresult) # 將ab的結(jié)果直接存入result print(result)where參數(shù)允許你進行條件計算只有滿足條件的元素才參與運算。arr np.array([1, 2, 3, 4, 5]) # 只在arr大于2的位置進行平方運算其他位置保持原值 result np.square(arr, wherearr2) print(result) # [1 2 9 16 25] 注意1和2的位置保留了原值但where不保證這些位置的值它們可能是未初始化的。更安全的做法是結(jié)合out參數(shù)。 safe_result arr.copy() np.square(arr, outsafe_result, wherearr2) print(safe_result) # [1 2 9 16 25]4.3 聚合函數(shù)從數(shù)據(jù)中提取信息聚合函數(shù)對數(shù)組的整個或某個軸上的數(shù)據(jù)進行統(tǒng)計計算返回一個標量或更小的數(shù)組。arr np.random.randn(1000) # 生成1000個標準正態(tài)分布隨機數(shù) print(np.mean(arr)) # 平均值接近0 print(np.std(arr)) # 標準差接近1 print(np.var(arr)) # 方差 print(np.min(arr), np.max(arr)) # 最小最大值 print(np.median(arr)) # 中位數(shù) print(np.sum(arr)) # 總和 print(np.prod(arr)) # 所有元素的乘積 # 對于多維數(shù)組可以指定軸axis arr_2d np.array([[1, 2, 3], [4, 5, 6]]) print(np.sum(arr_2d)) # 所有元素和 21 print(np.sum(arr_2d, axis0)) # 沿第0軸行求和即每列的和 [5 7 9] print(np.sum(arr_2d, axis1)) # 沿第1軸列求和即每行的和 [6 15]理解axis參數(shù)至關重要。你可以把axis想象成將要被“壓縮”掉的維度。axis0意味著沿著行的方向壓縮垂直方向結(jié)果的行數(shù)減少或變?yōu)?axis1意味著沿著列的方向壓縮水平方向結(jié)果的列數(shù)減少。5. 實戰(zhàn)演練用NumPy解決一個簡單圖像處理問題理論說了這么多我們用一個接近實際的小項目來串聯(lián)一下。假設我們有一張灰度圖片用二維數(shù)組表示值在0-255之間我們想實現(xiàn)一個簡單的“底片”效果反色和一個“閾值化”效果將低于某個值的像素變黑高于的變白。import numpy as np # 模擬一張5x5的小灰度圖數(shù)值范圍0-255 image np.array([ [100, 120, 130, 110, 90], [80, 140, 160, 100, 70], [200, 180, 50, 30, 220], [40, 60, 80, 150, 170], [10, 20, 250, 240, 230] ], dtypenp.uint8) # 使用無符號8位整數(shù)類型模擬圖像數(shù)據(jù) print(原始圖像數(shù)據(jù)) print(image) # 1. 底片效果反色255 - 像素值 inverted_image 255 - image print(\n底片效果) print(inverted_image) # 2. 閾值化處理將像素值大于128的設為255白小于等于128的設為0黑 threshold 128 # 方法1使用布爾索引 binary_image np.zeros_like(image) # 創(chuàng)建一個全零的同形狀數(shù)組 binary_image[image threshold] 255 print(\n閾值化效果方法1) print(binary_image) # 方法2使用np.where函數(shù)更簡潔 binary_image_2 np.where(image threshold, 255, 0) print(\n閾值化效果方法2) print(binary_image_2) # 3. 計算圖像的一些基本統(tǒng)計信息 print(f\n圖像統(tǒng)計:) print(f 平均亮度: {np.mean(image):.2f}) print(f 最大亮度: {np.max(image)}) print(f 最小亮度: {np.min(image)}) print(f 亮度標準差: {np.std(image):.2f})這個簡單的例子展示了NumPy如何讓像素級的圖像操作變得異常簡潔高效。所有操作都是向量化的沒有顯式的循環(huán)。在實際中對于百萬像素的圖片這種效率優(yōu)勢是決定性的。6. 性能陷阱與最佳實踐掌握了基本操作后要想寫出高性能的NumPy代碼還需要避開一些常見的陷阱。陷阱一在Python循環(huán)中操作NumPy數(shù)組元素。這是最大的性能殺手。永遠記住能向量化就向量化。# 糟糕的做法 arr np.random.rand(10000) result np.empty_like(arr) for i in range(len(arr)): result[i] arr[i] * 2 1 # 每次循環(huán)都有Python解釋器開銷 # 正確的做法向量化 result arr * 2 1 # 一次性在C層完成速度極快陷阱二不必要的拷貝。如前所述切片產(chǎn)生視圖花式索引產(chǎn)生拷貝。對于大數(shù)組無意識的拷貝會消耗大量內(nèi)存和時間。使用arr.base屬性可以檢查一個數(shù)組是否是視圖base指向原數(shù)組還是拷貝base為None。陷阱三數(shù)據(jù)類型不匹配導致的隱式轉(zhuǎn)換。NumPy運算會向上轉(zhuǎn)換到更精確或更通用的數(shù)據(jù)類型。混合int和float會得到float混合int32和int64可能會得到int64。這可能導致內(nèi)存使用增加和性能下降。在創(chuàng)建數(shù)組時盡量使用合適的dtype。最佳實踐建議預分配數(shù)組在循環(huán)開始前使用np.zeros、np.empty或np.full分配好結(jié)果數(shù)組避免在循環(huán)中通過np.append或列表拼接來增長數(shù)組后者會帶來巨大的性能開銷。善用原地操作許多NumPy函數(shù)和運算符有對應的原地操作版本如、*或者函數(shù)的out參數(shù)。它們可以避免創(chuàng)建臨時數(shù)組。使用np.einsum進行復雜的張量運算對于多維數(shù)組的求和、乘積等操作np.einsum愛因斯坦求和約定語法極其強大且高效能表達復雜的線性代數(shù)運算并且通常由高度優(yōu)化的后端實現(xiàn)。了解np.vectorize的局限性np.vectorize函數(shù)可以將一個接受標量輸入、輸出標量的Python函數(shù)“向量化”。但它本質(zhì)上還是一個Python級別的循環(huán)只是語法上看起來像向量化。它的性能提升有限主要用于方便不能替代真正的ufunc。對于性能關鍵的部分應盡量使用NumPy內(nèi)置的ufunc或用Cython/Numba等工具重寫。NumPy的世界遠不止這些基礎函數(shù)還有隨機數(shù)生成np.random、線性代數(shù)np.linalg、傅里葉變換np.fft等強大的子模塊。但萬變不離其宗深刻理解ndarray對象、廣播機制和向量化思想是自如運用所有這些高級功能的基礎。當你習慣用數(shù)組的思維方式去解決問題時你會發(fā)現(xiàn)很多原本復雜的計算任務都能被優(yōu)雅地簡化為幾行高效的NumPy代碼。這就是NumPy的魅力所在。