1. 写在前面
这篇博客是针对想入门机器学习和深度学习,或者数据分析的小白而写,我们都知道机器学习和深度学习,数据分析的编程基础就是Python编程,而最常用的一些库,像numpy,pandas,matplotlib,sklearn等这些库都必须熟记于心,如果学习深度学习的话,还必须有着深度学习基础,掌握一些主流的TensorFlow,pytorch等框架才能在这条路上走下去。
而在信息发展很快的时代,原来在校的那种,学完理论,再去实战的学习方式,其实已经不太适用,现在应该get到的一种新的学习技能其实应该是,边用边学,理论不需要总的学一遍再出发,而是掌握一些基本的理论,然后出发,遇到问题再返回来补充理论的这样的一种逻辑。
所以如果想入门人工智能或者数据分析,千万不要想着我Python学一遍,各种库先学一遍这样,不仅慢,有时候还会浪费很多时间,所以我根据之前整理的一些numpy,总结了一个numpy的快速入门(后续还有pandas,matplotlib的)有了这些知识,然后去通过项目实战,然后再补充,应该会快速上手。如果想更深入的学习numpy,我后面也会给出一些链接,是我曾经学习时整理的笔记,希望有所帮助。
下面开始:
2. Numpy是什么?
简单的说,Numpy是Python中一个非常重要的第三库,它不仅是 Python 中使用最多的第三方库,而且还是 SciPy、Pandas 等数据科学的基础库。它所提供的数据结构比 Python 自身的“更高级、更高效”,可以这么说,NumPy 所提供的数据结构是 Python 数据分析的基础。
3. 为什么要使用Numpy?
一句话:Numpy可以使得Python科学计算更高效。 Numpy中的数据结构比Python本身自带的列表等这种内置结构更加有效,有下面几个原因:
- Python的list的元素在系统内存中是分散存储,而Numpy中的数据存储是一块均匀连续的内存块。这样数组计算遍历所有的元素,不像列表 list 还需要对内存地址进行查找,从而节省了计算资源。
- 在内存访问模式中,缓存会直接把字节块从 RAM 加载到 CPU 寄存器中。因为数据连续的存储在内存中,NumPy 直接利用现代 CPU 的矢量化指令计算,加载寄存器中的多个连续浮点数。
- NumPy 中的矩阵计算可以采用多线程的方式,充分利用多核 CPU 计算资源,大大提升了计算效率。
PS: 当然除了使用 NumPy外,你还需要一些技巧来提升内存和提高计算资源的利用率。一个重要的规则就是:避免采用隐式拷贝,而是采用就地操作的方式。举个例子,如果我想让一个数值 x 是原来的两倍,可以直接写成 x*=2,而不要写成 y=x*2。前者可以快到2倍甚至更多。
4. 如何快速掌握Numpy?
其实想快速掌握Numpy,掌握两个重要的对象就可以了
- ndarray(N-dimensional array object)解决了多维数组问题
- ufunc(universal function object)则是解决对数组进行处理的函数
下面一一来看看。
4.1 ndarray对象
ndarray 实际上是多维数组的含义。在 NumPy 数组中,维数称为秩(rank),一维数组的秩为 1,二维数组的秩为 2,以此类推。在 NumPy 中,每一个线性的数组称为一个轴(axes),其实秩就是描述轴的数量。
首先,先来看看,ndarray对象是如何创建数组的,然后再来看看如何处理结构数组?
4.1.1 创建数组
创建数组前,你需要引用 NumPy 库,可以直接通过 array 函数创建数组,如果是多重数组,比如示例里的 b,那么该怎么做呢?你可以先把一个数组作为一个元素,然后嵌套起来,比如示例 b 中的[1,2,3]就是一个元素,然后[4,5,6][7,8,9]也是作为元素,然后把三个元素再放到[]数组里,赋值给变量 b。
当然数组也是有属性的,比如你可以通过函数 shape 属性获得数组的大小,通过 dtype 获得元素的属性。如果你想对数组里的数值进行修改的话,直接赋值即可,注意下标是从 0 开始计的,所以如果你想对 b 数组,九宫格里的中间元素进行修改的话,下标应该是[1,1]。
import numpy as np # 先导入
a = np.array([1, 2, 3])
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a.shape)
print(b.shape)
print(a.dtype)
b[1, 1] = 10
print(b)
4.1.2 结构数组
如果你想统计一个班级里面学生的姓名、年龄,以及语文、英语、数学成绩该怎么办?当然你可以用数组的下标来代表不同的字段,比如下标为 0 的是姓名、下标为 1 的是年龄等,但是这样不显性。实际上在 C 语言里,可以定义结构数组,也就是通过 struct 定义结构类型,结构中的字段占据连续的内存空间,每个结构体占用的内存大小都相同,那在 NumPy 中是怎样操作的呢?
import numpy as np
persontype = np.dtype({
'names':['name', 'age', 'chinese', 'math', 'english'],
'formats':['S32','i', 'i', 'i', 'f']})
peoples = np.array([("ZhangFei",32,75,100, 90),("GuanYu",24,85,96,88.5),
("ZhaoYun",28,85,92,96.5),("HuangZhong",29,65,85,100)],
dtype=persontype)
ages = peoples[:]['age']
chineses = peoples[:]['chinese']
maths = peoples[:]['math']
englishs = peoples[:]['english']
print np.mean(ages)
print np.mean(chineses)
print np.mean(maths)
print np.mean(englishs)
首先在 NumPy 中是用 dtype 定义的结构类型,然后在定义数组的时候,用 array 中指定了结构数组的类型 dtype=persontype,这样你就可以自由地使用自定义的 persontype 了。比如想知道每个人的语文成绩,就可以用 chineses = peoples[:][‘chinese’],当然 NumPy 中还有一些自带的数学运算,比如计算平均值使用 np.mean。
PS: 注意上面是Python2中的格式,Python3中,print不是语句了,是个函数,用括号括起来。
4.2 Numpy数组属性
-
.shape属性
数组的形状信息,非常重要。在深度学习中,构建网络模型,调试多维数组运算代码时,shape 的作用更加凸显。shape 属性返回数组的形状信息,是一个元组对象。如下, 分别创建一维数组v和二维数组m:In [108]: v = np.zeros(10) In [109]: m = np.ones((3,4)) In [110]: v.shape Out[110]: (10,) In [111]: m.shape Out[111]: (3, 4)
-
size属性获取数组内元素的个数
m.size # 12
-
.dtype属性获取数组内元素的类型
m.dtype # dtype('float64')
dtype 更多取值:int、complex、bool、object,还可以显示的定义数据位数的类型,如:int64、int16、float128、complex128。
4.3 ufunc运算
ufunc 是 universal function 的缩写,是不是听起来就感觉功能非常强大?确如其名,它能对数组中每个元素进行函数操作。NumPy 中很多 ufunc 函数计算速度非常快,因为都是采用 C 语言实现的。
4.3.1 连续数组的创建
NumPy 可以很方便地创建连续数组,比如我使用 arange 或 linspace 函数
进行创建:
x1 = np.arange(1,11,2)
x2 = np.linspace(1,9,5)
np.arange 和 np.linspace 起到的作用是一样的,都是创建等差数组。这两个数组的结果 x1,x2 都是[1 3 5 7 9]。结果相同,但是你能看出来创建的方式是不同的。
- arange() 类似内置函数 range(),通过指定初始值、终值、步长来创建等差数列的一维数组,默认是不包括终值的。
- linspace 是 linear space 的缩写,代表线性等分向量的含义。linspace() 通过指定初始值、终值、元素个数来创建等差数列的一维数组,默认是包括终值的。
logspace
函数,创建以 e 为底,指数为 1,2,…,10 的数组:
In [17]: np.logspace(1, 10, 10, base=e)
Out[17]:
array([2.71828183e+00, 7.38905610e+00, 2.00855369e+01, 5.45981500e+01,
1.48413159e+02, 4.03428793e+02, 1.09663316e+03, 2.98095799e+03,
8.10308393e+03, 2.20264658e+04])
diag
函数, 创建对角数组
In [22]: np.diag([1,2,3], k=0) # 还可以通过k参数设置主对角线偏移量
Out[22]:
array([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
zeros
函数, 创建元素都为0的数组, ones
函数, 创建元素都为1的数组。np.random
模块生成随机数组。
4.3.2 算数运算
通过 NumPy 可以自由地创建等差数组,同时也可以进行加、减、乘、除、求 n 次方和取余数。
x1 = np.arange(1,11,2)
x2 = np.linspace(1,9,5)
print np.add(x1, x2)
print np.subtract(x1, x2)
print np.multiply(x1, x2)
print np.divide(x1, x2)
print np.power(x1, x2)
print np.remainder(x1, x2)
以 x1, x2 数组为例,求这两个数组之间的加、减、乘、除、求 n 次方和取余数。在 n 次方中,x2 数组中的元素实际上是次方的次数,x1 数组的元素为基数。在取余函数里,你既可以用 np.remainder(x1, x2),也可以用 np.mod(x1, x2),结果是一样的。
4.3.3 统计函数
如果你想要对一堆数据有更清晰的认识,就需要对这些数据进行描述性的统计分析,比如了解这些数据中的最大值、最小值、平均值,是否符合正态分布,方差、标准差多少等等。它们可以让你更清楚地对这组数据有认知。下面我来介绍下在 NumPy 中如何使用这些统计函数。
4.3.3.1 计数组 / 矩阵中的最大值函数 amax(),最小值函数 amin()
amin() 用于计算数组中的元素沿指定轴的最小值。对于一个二维数组 a,amin(a) 指的是数组中全部元素的最小值,amin(a,0) 是延着 axis=0 轴的最小值,axis=0 轴是把元素看成了[1,4,7], [2,5,8], [3,6,9]三个元素,所以最小值为[1,2,3],amin(a,1) 是延着 axis=1 轴的最小值,axis=1 轴是把元素看成了[1,2,3], [4,5,6], [7,8,9]三个元素,所以最小值为[1,4,7]。同理 amax() 是计算数组中元素沿指定轴的最大值。
import numpy as np
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
print np.amin(a)
print np.amin(a,0)
print np.amin(a,1)
print np.amax(a)
print np.amax(a,0)
print np.amax(a,1)
4.3.3.2统计最大值与最小值之差 ptp()
对于相同的数组 a,np.ptp(a) 可以统计数组中最大值与最小值的差,即 9-1=8。同样 ptp(a,0) 统计的是沿着 axis=0 轴的最大值与最小值之差,即 7-1=6(当然 8-2=6,9-3=6,第三行减去第一行的 ptp 差均为 6),ptp(a,1) 统计的是沿着 axis=1 轴的最大值与最小值之差,即 3-1=2(当然 6-4=2, 9-7=2,即第三列与第一列的 ptp 差均为 2)。
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
print np.ptp(a)
print np.ptp(a,0)
print np.ptp(a,1)
4.3.3.3 统计数组的百分位数 percentile()
percentile() 代表着第 p 个百分位数,这里 p 的取值范围是 0-100,如果 p=0,那么就是求最小值,如果 p=50 就是求平均值,如果 p=100 就是求最大值。同样你也可以求得在 axis=0 和 axis=1 两个轴上的 p% 的百分位数。
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
print np.percentile(a, 50)
print np.percentile(a, 50, axis=0)
print np.percentile(a, 50, axis=1)
4.3.3.4统计数组中的中位数 median()、平均数 mean()
你可以用 median() 和 mean() 求数组的中位数、平均值,同样也可以求得在 axis=0 和 1 两个轴上的中位数、平均值
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
#求中位数
print np.median(a)
print np.median(a, axis=0)
print np.median(a, axis=1)
#求平均数
print np.mean(a)
print np.mean(a, axis=0)
print np.mean(a, axis=1)
4.3.3.5 统计数组中的加权平均值 average()
a = np.array([1,2,3,4])
wts = np.array([1,2,3,4])
print np.average(a)
print np.average(a,weights=wts)
average() 函数可以求加权平均,加权平均的意思就是每个元素可以设置个权重,默认情况下每个元素的权重是相同的,所以 np.average(a)=(1+2+3+4)/4=2.5,你也可以指定权重数组 wts=[1,2,3,4],这样加权平均 np.average(a,weights=wts)=(11+22+33+44)/(1+2+3+4)=3.0。
4.3.3.6统计数组中的标准差 std()、方差 var()
a = np.array([1,2,3,4])
print np.std(a)
print np.var(a)
方差的计算是指每个数值与平均值之差的平方求和的平均值,即 mean((x - x.mean())** 2)。标准差是方差的算术平方根。在数学意义上,代表的是一组数据离平均值的分散程度。所以 np.var(a)=1.25, np.std(a)=1.118033988749895。
4.3.3.7 累乘与累积
如下,分别求所有维度上元素的累乘、某一维度上的累乘:
In [22]: m1
Out[22]:
array([[8, 4, 8, 2],
[4, 9, 2, 1],
[7, 7, 7, 5]])
In [40]: m1.cumprod()
Out[40]:
array([ 8, 32, 256, 512, 2048, 18432,
36864, 36864, 258048, 1806336, 12644352, 63221760],
dtype=int32)
In [42]: m1.cumprod(axis=1)
Out[42]:
array([[ 8, 32, 256, 512],
[ 4, 36, 72, 72],
[ 7, 49, 343, 1715]], dtype=int32)
如下,分别求所有维度上元素的累加和、某一维度上的累加和:
In [43]: m1.cumsum()
Out[43]: array([ 8, 12, 20, 22, 26, 35, 37, 38, 45, 52, 59, 64], dtype=int32)
In [44]: m1.cumsum(axis=1)
Out[44]:
array([[ 8, 12, 20, 22],
[ 4, 13, 15, 16],
[ 7, 14, 21, 26]], dtype=int32)
4.4 Numpy排序
排序算法在 NumPy 中实现起来其实非常简单,一条语句就可以搞定。这里你可以使用 sort 函数,sort(a, axis=-1, kind=‘quicksort’, order=None),默认情况下使用的是快速排序;在 kind 里,可以指定 quicksort、mergesort、heapsort 分别表示快速排序、合并排序、堆排序。同样 axis 默认是 -1,即沿着数组的最后一个轴进行排序,也可以取不同的 axis 轴,或者 axis=None 代表采用扁平化的方式作为一个向量进行排序。另外 order 字段,对于结构化的数组可以指定按照某个字段进行排序。
a = np.array([[4,3,2],[2,4,1]])
print np.sort(a)
print np.sort(a, axis=None)
print np.sort(a, axis=0)
print np.sort(a, axis=1)
4.5 改变数组
4.5.1 flatten函数
NumPy 的 flatten 函数也有改变 shape 的能力,它将高维数组变为向量。但是,它会发生数组复制行为。
In [68]: v1 = np.random.randint(1,10,(2,3))
In [69]: v1
Out[69]:
array([[3, 8, 5],
[2, 3, 4]])
In [70]: v2 = v1.flatten()
In [71]: v2
Out[71]: array([3, 8, 5, 2, 3, 4])
v2[0] 被修改为 30 后,原数组 v1 没有任何改变。
In [73]: v2[0] = 30
In [74]: v1
Out[74]:
array([[3, 8, 5],
[2, 3, 4]])
4.5.2 newaxis
使用 newaxis 增加一个维度,维度的索引只有 0
In [81]: v1 = np.arange(10) # shape 为一维,(10, )
In [82]: v2 = v1[:,np.newaxis] # shape 为二维,(10,1)
In [83]: v3 = v1[np.newaxis, :] # (1, 10)
4.5.3 squeeze函数
从数组的形状中删除单维度条目,即把shape中为1的维度去掉
用法:numpy.squeeze(a,axis = None)
- a表示输入的数组;
- axis用于指定需要删除的维度,但是指定的维度必须为单维度,否则将会报错;
- axis的取值可为None 或 int 或 tuple of ints, 可选。若axis为空,则删除所有单维度的条目;
- 返回值:数组
- 不会修改原数组;
>>> a = e.reshape(1,1,10)
>>> a
array([[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]])
>>> np.squeeze(a)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
4.5.4 repeat
repeat 操作,实现某一维上的元素复制操作。
在维度 0 上复制元素 2 次:
In [132]: a = np.array([[1,2],[3,4]])
In [138]: np.repeat(a,2,axis=0)
Out[138]:
array([[1, 2],
[1, 2],
[3, 4],
[3, 4]])
在维度 1 上复制元素 2 次:
In [137]: np.repeat(a,2,axis=1)
Out[137]:
array([[1, 1, 2, 2],
[3, 3, 4, 4]])
4.5.5 tile
tile 实现按块复制元素:
In [4]: a = np.array([[1,2],[3,4]])
In [89]: np.tile(a,3)
Out[89]:
array([[1, 2, 1, 2, 1, 2],
[3, 4, 3, 4, 3, 4]])
In [6]: np.tile(a,(2,3)) # 原块是1 2 3 4, 把这个看成一个整体铺2行3列
Out[6]:
array([[1, 2, 1, 2, 1, 2],
[3, 4, 3, 4, 3, 4],
[1, 2, 1, 2, 1, 2],
[3, 4, 3, 4, 3, 4]])
4.5.6 vstack
vstack,vertical stack,沿竖直方向合并多个数组:
In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[-1,-2]])
In [6]: c = np.vstack((a,b)) # 注意参数类型:元组
Out[5]:
array([[ 1, 2],
[ 3, 4],
[-1, -2]])
4.5.7 hstack
hstack 沿水平方向合并多个数组。
值得注意,不管是 vstack,还是 hstack,沿着合并方向的维度,其元素的长度要一致。
In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[5,6,7],[8,9,10]])
In [4]: c = np.hstack((a,b))
In [5]: c
Out[5]:
array([[ 1, 2, 5, 6, 7],
[ 3, 4, 8, 9, 10]])
4.5.8 concatenate
concatenate 指定在哪个维度上合作数组。
In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[-1,-2]])
In [6]: np.concatenate((a,c),axis=0) # 效果等于 vstack
Out[6]:
array([[ 1, 2],
[ 3, 4],
[-1, -2]])
In [7]: c = np.array([[5,6,7],[8,9,10]])
In [108]: np.concatenate((a,c),axis=1) # 效果等于 hstack
Out[108]:
array([[ 1, 2, 5, 6, 7],
[ 3, 4, 8, 9, 10]])
4.5.9 argmax, argmin
argmax 返回数组中某个维度的最大值索引,当未指明维度时,返回 buffer 中最大值索引。如下所示:
In [131]: a = np.random.randint(1,10,(2,3))
In [132]: a
Out[132]:
array([[8, 1, 4],
[5, 4, 3]])
In [133]: a.argmax()
Out[133]: 0
In [134]: a.argmax(axis = 0)
Out[134]: array([0, 1, 0], dtype=int64)
In [135]: a.argmax(axis = 1)
Out[135]: array([0, 0], dtype=int64)
5. Numpy练习题整理
-
创建一个[3, 5]所有元素为True的数组
In [15]: np.ones((3,5),dtype=bool) Out[15]: array([[ True, True, True, True, True], [ True, True, True, True, True], [ True, True, True, True, True]])
-
一维数组转二维
In [18]: a = np.linspace(1,5,10) In [19]: a.reshape(5,2) Out[19]: array([[1. , 1.44444444], [1.88888889, 2.33333333], [2.77777778, 3.22222222], [3.66666667, 4.11111111], [4.55555556, 5. ]])
-
数组的所有奇数替换为-1
In [14]: m = np.arange(10).reshape(2,5) In [16]: m[m%2==1] = -1 In [17]: m Out[17]: array([[ 0, -1, 2, -1, 4], [-1, 6, -1, 8, -1]])
-
提取数组中所有奇数
In [18]: m = np.arange(10).reshape(2,5) In [19]: m[m%2==1] Out[19]: array([1, 3, 5, 7, 9])
-
2个Numpy数组的交集
In [21]: m ,n = np.arange(10), np.arange(1,15,3) In [22]: np.intersect1d(m,n) Out[22]: array([1, 4, 7])
-
2个Numpy数组的差集
In [21]: m ,n = np.arange(10), np.arange(1,15,3) In [23]: np.setdiff1d(m,n) Out[23]: array([0, 2, 3, 5, 6, 8, 9])
-
筛选指定区间内的所有元素
In [21]: m = np.arange(10).reshape(2,5) In [34]: m[(m > 2) & (m < 7)] Out[34]: array([3, 4, 5, 6])
-
二维数组交换多列
In [21]: m = np.arange(10).reshape(2,5) In [37]: m Out[37]: array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) In [36]: m[:,[1,0,2,4,3]] Out[36]: array([[1, 0, 2, 3, 4], [6, 5, 7, 8, 9]])
-
二维数组翻转行
In [39]: m = np.arange(10).reshape(2,5) In [40]: m Out[40]: array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) In [41]: m[::-1] Out[41]: array([[5, 6, 7, 8, 9], [0, 1, 2, 3, 4]])
-
生成数值 5~10、shape 为 (3,5) 的随机浮点数
In [9]: np.random.seed(100) In [42]: np.random.randint(5,10,(3,5)) + np.random.rand(3,5) Out[42]: array([[9.31623868, 5.68431289, 9.5974916 , 5.85600452, 9.3478736 ], [5.66356114, 7.78257215, 7.81974462, 6.60320117, 7.17326763], [7.77318114, 6.81505713, 9.21447171, 5.08486345, 8.47547692]])
-
返回有规律的数组
a = np.array([1,2,3]), 输出array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3])
这个简单分析一下就是数组前半部分1, 1, 1, 2, 2, 2, 3, 3, 3
通过repeat
函数复制 3 次,后面部分通过tile
函数复制 3 次,然后合并数据。np.hstack((np.repeat(a, 3), np.tile(a, 3)))
-
python实现向量化
原生的 Python 列表不支持向量化操作,两个列表相加默认不是逐个元素相加:a = [1,3,5] b = [2,4,6] a + b # 默认实现的不是逐个元素相加操作
所以, 我们可以借助vectorize函数来实现矢量相加:
def add(x,y): return x+y addv = np.vectorize(add) In [67]: addv(a,b) Out[67]: array([ 3, 7, 11])
-
求任意分位数
已知数组 a,求 20 分位数,80 分位数:a = np.arange(11) In [95]: np.percentile(a,q=[20,80]) Out[95]: array([2., 8.])
-
找Numpy中的缺失值
NumPy 使用np.nan
标记缺失值,给定如下数组 a,求出缺失值的索引。a = np.array([ 0., 1., np.nan, 3., np.nan, np.nan, 6., 7., 8., 9.]) np.where(np.isnan(a)) # (array([2, 4, 5], dtype=int64),)
-
返回无缺失值的行和行号
a = np.array([[ 0., np.nan, 2., 3.], [ 4., 5., np.nan, 7.], [ 8., 9., 10., 11.], [12., 13., np.nan, 15.], [16., 17., np.nan, 19.], [20., 21., 22., 23.]]) a[np.sum(np.isnan(a), axis=1) == 0] ##结果 array([[ 8., 9., 10., 11.], [20., 21., 22., 23.]]) np.where(np.sum(np.isnan(a), axis=1)==0) # 没有缺失元素的行的行号 (array([2, 5], dtype=int64),) ## 填充缺失值为0 a[np.isnan(a)] = 0
-
限制元素打印个数和浮点精度
这个用的是set_printoptions
函数, 这里面还有其他的一些功能。np.set_printoptions(threshold=5) #概略显示 print(np.arange(10)) # [0 1 2 ..., 7 8 9] np.set_printoptions(precision = 4) #设置浮点精度 print(np.array([1.123456789])) # [1.1235]
总结
在 NumPy 学习中,你重点要掌握的就是对数组的使用,因为这是 NumPy 和标准 Python 最大的区别。在 NumPy 中重新对数组进行了定义,同时提供了算术和统计运算,你也可以使用 NumPy 自带的排序功能,一句话就搞定各种排序算法。
当然要理解 NumPy 提供的数据结构为什么比 Python 自身的“更高级、更高效”,要从对数据指针的引用角度进行理解。