什么是 NumPy?#

NumPy 是 Python 中科学计算的基础包。它是一个 Python 库,提供多维数组对象、各种派生对象(例如掩码数组和矩阵)以及各种用于快速数组操作的例程,包括数学、逻辑、形状操作、排序、选择、I/O 、离散傅里叶变换、基本线性代数、基本统计运算、随机模拟等等。

NumPy 包的核心是ndarray对象。这封装了同质数据类型的n维数组,许多操作在编译代码中执行以提高性能。 NumPy 数组和标准 Python 序列之间有几个重要的区别:

  • 与 Python 列表(可以动态增长)不同,NumPy 数组在创建时具有固定大小。更改ndarray的大小将创建一个新数组并删除原始数组。

  • NumPy 数组中的元素都必须具有相同的数据类型,因此在内存中的大小也相同。例外:可以拥有(Python,包括 NumPy)对象的数组,从而允许不同大小元素的数组。

  • NumPy 数组有助于对大量数据进行高级数学和其他类型的运算。通常,与使用 Python 的内置序列相比,此类操作的执行效率更高,并且代码更少。

  • 越来越多的基于 Python 的科学和数学包正在使用 NumPy 数组;尽管这些通常支持 Python 序列输入,但它们在处理之前将此类输入转换为 NumPy 数组,并且通常输出 NumPy 数组。换句话说,为了有效地使用当今大部分(甚至是大多数)基于 Python 的科学/数学软件,仅仅知道如何使用 Python 的内置序列类型是不够的 - 还需要知道如何使用 NumPy 数组。

关于序列大小和速度的问题在科学计算中尤为重要。作为一个简单的示例,考虑将一维序列中的每个元素与相同长度的另一个序列中的相应元素相乘的情况。如果数据存储在两个 Python 列表a和中b,我们可以迭代每个元素:

c = []
for i in range(len(a)):
    c.append(a[i]*b[i])

这会产生正确的答案,但如果ab每个都包含数百万个数字,我们将为 Python 中的循环效率低下付出代价。我们可以通过编写 C 语言更快地完成相同的任务(为了清楚起见,我们忽略了变量声明和初始化、内存分配等)

for (i = 0; i < rows; i++) {
  c[i] = a[i]*b[i];
}

这节省了解释 Python 代码和操作 Python 对象所涉及的所有开销,但代价是牺牲了从 Python 编码中获得的好处。此外,所需的编码工作随着数据维度的增加而增加。例如,在二维数组的情况下,C 代码(如前所述)扩展为

for (i = 0; i < rows; i++) {
  for (j = 0; j < columns; j++) {
    c[i][j] = a[i][j]*b[i][j];
  }
}

NumPy 为我们提供了两全其美的功能:当涉及ndarray时,逐元素操作是“默认模式” ,但逐元素操作可以通过预编译的 C 代码快速执行。在 NumPy 中

c = a * b

执行前面示例的操作,速度接近 C,但代码简单,符合我们对基于 Python 的期望。事实上,NumPy 习惯更简单!最后一个示例说明了 NumPy 的两个功能,它们是其大部分功能的基础:矢量化和广播。

为什么 NumPy 很快?#

矢量化描述了代码中没有任何显式循环、索引等 - 当然,这些事情只是在优化的预编译 C 代码中“在幕后”发生。矢量化代码具有许多优点,其中包括:

  • 矢量化代码更简洁,更容易阅读

  • 更少的代码行通常意味着更少的错误

  • 代码更类似于标准数学符号(通常更容易正确编码数学结构)

  • 矢量化会产生更多“Pythonic”代码。如果没有矢量化,我们的代码将充满低效且难以阅读的for循环。

广播是用于描述操作的隐式逐元素行为的术语;一般来说,在 NumPy 中,所有运算,不仅仅是算术运算,还有逻辑运算、按位运算、函数运算等,都以这种隐式的逐元素方式表现,即它们广播。此外,在上面的示例中,ab可以是相同形状的多维数组,或者标量和数组,甚至是不同形状的两个数组,前提是较小的数组可以“扩展”为较大的数组的形状由此产生的广播是明确的。有关广播的详细“规则”,请参阅 广播

还有谁使用 NumPy?#

NumPy 完全支持面向对象的方法,再次从ndarray开始。例如,ndarray是一个类,拥有许多方法和属性。它的许多方法都由最外层 NumPy 命名空间中的函数镜像,允许程序员以他们喜欢的任何范式进行编码。这种灵活性使得 NumPy 数组方言和 NumPy ndarray类成为Python 中使用的多维数据交换的事实上的语言。