📊 第6章 图解数组计算模块 NumPy
📄 一句话概括

asarray 遇到已经是数组的输入,直接返回它自己——省内存,但也埋了个坑。 array 一定复制一份,改副本不影响原数组。 函数入口用 asarray 统一输入类型,函数内部要改就先 copy。

📝 本章和前面几章的关系

上一篇造的是新数据。这一篇处理「手上已经有一个数组」的情况。 区别只有一句话:复制还是不复制。但后果能拖一整晚。

一、asarray 不复制

B = np.asarray(A) 之后,A 和 B 指向同一块内存。 B is A 给 True,B.base 指向 A——说明 B 是 A 的视图。 asarray 遇到已经是 ndarray 的输入,直接返回它自己。省一次复制。

输入是列表呢?那就得新建,照样占内存。

二、视图的坑:改一个,另一个也变

B[0] = 99 之后,A 的第 0 个也变成 99 了。 因为 B 和 A 是同一块内存的两个名字。 换成 C = np.array(A) 再改 C,A 就不动了——array 复制了一份。

要独立就用 array。代价是多占一份内存,好处是互不干扰。

三、什么时候用哪个

情况 用哪个 为什么
输入可能不是数组 asarray 是的话直接返回,省一次复制
要改且不能影响原数据 array 复制一份
只是读一下 asarray 更快、更省内存

一个常见写法:A = np.asarray(可能是列表也可能是数组的东西),把输入统一。 函数入口用 asarray,函数内部要改就先 copy。这条能防住一多半「莫名其妙被改了」。

四、从别的结构来

从元组:np.array((1, 2, 3)),和列表一样。 从生成器:np.fromiter((x*x for x in range(4)), int)。 从字符串:np.array('1 2 3'.split(), dtype=int),先 split 再转。

fromiter 必须显式给 dtype。不给的话它没法推,直接报错。

五、动手 Demo:从已有数组创建

先验证 asarray 和 array 的复制差异,再演示改视图会连带改原数组。 最后从元组、生成器、字符串各造一个。

import numpy as np

A = np.array([1, 2, 3])
B = np.asarray(A)
C = np.array(A)
print("A is B:%s   A is C:%s" % (A is B, A is C))

B[0] = 99
print("改 B 之后 A = %s" % A)

A = np.array([1, 2, 3])
C = np.array(A)
C[0] = 99
print("改 C 之后 A = %s,C = %s" % (A, C))

print("从元组 %s" % np.array((1, 2, 3)))
print("从生成器 %s" % np.fromiter((x * x for x in range(4)), int))
print("从字符串 %s" % np.array("1 2 3".split(), dtype=int))

实跑输出:

--------------------------------------------------------------------
从已有数组创建
--------------------------------------------------------------------

  ① asarray 不复制,array 会复制
  --------------------------------------------------------------------
  A 和 B 是不是同一块内存:True。
  A 和 C 是不是同一块内存:False。
  但 B.base 指向 A,说明 B 是 A 的「视图」,不是新数组。
  ! asarray 遇到已经是 ndarray 的输入,直接返回它自己。省内存,但也埋了个坑。

  ② 视图的坑:改一个,另一个也变
  --------------------------------------------------------------------
  改 B 的第 0 个之后,A 变成 [99  2  3]。
  因为 B 和 A 指的是同一块内存。改其中一个,另一个必然跟着变。
  用 array 复制一份再改:A 还是 [1 2 3],C 是 [99  2  3]。
  ! 要独立就得 array 复制。asarray 省的是内存,代价是「共享」。

  ③ 什么时候用哪个
  --------------------------------------------------------------------

| 情况 | 用哪个 | 为什么 |
|---|---|---|
| 输入可能不是数组 | asarray | 是的话直接返回,省一次复制 |
| 要改且不能影响原数据 | array | 复制一份 |
| 只是读一下 | asarray | 更快 |

  一个常见写法:A = np.asarray(可能是列表或数组的东西),把两种输入统一。
  ! 函数入口用 asarray,函数内部要改就先 copy。这条能防住一多半「莫名其妙被改了」。

  ④ 从别的结构来
  --------------------------------------------------------------------
  从元组:[1 2 3]
  从生成器得用 fromiter:[0 1 4 9]
  从字符串:[1 2 3]
  frombuffer、fromfile 是读二进制用的,日常很少碰。
  ! fromiter 要显式给 dtype。不给的话它没法推,直接报错。

A is B 给 True 说明是同一个对象。注意 is 比的是「是不是同一个」,== 比的是「值相等」。 asarray 省内存、array 保平安。没想清楚要不要改,就先用 array。

30 秒实验

  1. 把 asarray 换成 array,重跑一遍,看 A is B 变成什么。
  2. 给 asarray 传一个列表,看 A is B 还是不是 True。
  3. 想一想:如果函数内部把参数改了,调用方会受影响吗?

常见误区

  1. 以为 asarray 一定复制:遇到数组就不复制,改起来连带原数组。
  2. 用 == 判断是不是同一个对象:值相等不代表同一个,要用 is。
  3. fromiter 不给 dtype:直接报错,它推不出来。
  4. 函数里直接改传入的数组:调用方的数据被悄悄改了。
自测三问(点开对答案) 1. asarray 和 array 最本质的区别? —— 输入已经是 ndarray 时,asarray 直接返回它自己(不复制),array 一定复制一份。 2. 为什么改 asarray 的结果会影响原数组? —— 它们是同一块内存的两个名字,改哪个都是改那块内存。 3. 函数里拿到一个数组参数,要改应该怎么做? —— 先 `A = np.array(A)` 或 `A.copy()`,再改副本,别动调用方的数据。

数组有了。下一篇讲它里面装的是什么类型,还有那个比循环快 51 倍的运算。

相关笔记