基础与用法

NumPy 基础知识

这些文档阐明了 NumPy 中的概念、设计决策和技术限制。这是了解 NumPy 基本思想和哲学的好地方。

  • 数组创建

  • ndarrays进行索引

  • 使用 NumPy 进行 I/O

  • 数据类型

  • 广播

  • 复制和视图

  • 结构化数组

  • 通用函数(ufunc)基础知识

MATLAB 用户的 NumPy

介绍

MATLAB® 和 NumPy 有很多共同之处,但 NumPy 是为了与 Python 一起使用而创建的,而不是 MATLAB 的克隆。本指南将帮助 MATLAB 用户开始使用 NumPy。

一些主要区别

大致等效项

下表提供了一些常见 MATLAB 表达式的大致等效项。这些是类似的表达式,而不是等效项。详情请参见文档。

在下表中,假设你已经在 Python 中执行了以下命令:

import numpy as np
from scipy import io, integrate, linalg, signal
from scipy.sparse.linalg import cg, eigs 

还假设如果备注谈论“矩阵”,那么参数是二维实体。

通用等效物

|

for i=1:3
    fprintf('%i\n',i)
end 

|

for i in range(1, 4):
   print(i) 

|

>>  4  ==  4
ans  =  1
>>  4  ==  5
ans  =  0 

|

>>> 4 == 4
True
>>> 4 == 5
False 

|

a=4
if  a==4
  fprintf('a = 4\n')
elseif  a==5
  fprintf('a = 5\n')
end 

|

a = 4
if a == 4:
    print('a = 4')
elif a == 5:
    print('a = 5') 

线性代数等价操作

|

rng(42,'twister')
rand(3,4) 

|

from numpy.random import default_rng
rng = default_rng(42)
rng.random(3, 4) 

或者旧版本:random.rand((3, 4)) | 用默认随机数生成器和 seed = 42 生成一个 3x4 的随机数组 |

注意事项

子矩阵:可以使用索引列表和 ix_ 命令对子矩阵进行赋值。例如,对于二维数组 a,可以执行:ind=[1, 3]; a[np.ix_(ind, ind)] += 100

HELP: Python 没有直接等价于 MATLAB 的 which 命令,但是 help 命令和 numpy.source 命令通常会列出函数所在的文件名。Python 还有一个 inspect 模块(使用 import inspect 导入),其中提供了一个 getfile 方法,该方法通常起作用。

INDEXING: MATLAB 使用基于 1 的索引,所以一个序列的初始元素索引为 1。Python 使用基于 0 的索引,所以一个序列的初始元素索引为 0。混淆和争议产生是因为每种方式都有优劣之处。基于 1 的索引与人们常用的自然语言使用方式一致,其中序列的“第一个”元素索引为 1。基于 0 的索引简化了索引操作。还可参考Edsger W. Dijkstra 教授的某篇文本

RANGES:在 MATLAB 中,0:5 可以作为区间文字和“切片”索引使用(在圆括号内);然而,在 Python 中,形如 0:5 的结构只能作为“切片”索引使用(在方括号内)。因此,为了使 NumPy 具有类似简洁的区间构造机制,创建了有点古怪的 r_ 对象。注意,r_ 不像函数或构造函数一样调用,而是使用方括号进行索引,这允许在参数中使用 Python 的切片语法。

逻辑运算符:在 NumPy 中,&|是按位 AND/OR 运算符,而在 MATLAB 中,&和|是逻辑 AND/OR 运算符。这两者看起来可能是相同的,但存在重要的区别。如果你曾经使用过 MATLAB 的&|运算符,你应该使用 NumPy 的 ufuncs logical_and/logical_or。MATLAB 的&|运算符与 NumPy 的&|运算符之间的显着差异包括:

  • 非逻辑{0,1}输入:NumPy 的输出是输入的按位 AND 运算。MATLAB 将任何非零值视为 1,并返回逻辑 AND。例如,在 NumPy 中(3 & 4)0,而在 MATLAB 中34都被视为逻辑真,(3 & 4)返回1

  • 优先级:NumPy 的&运算符的优先级高于诸如<>的逻辑运算符;MATLAB 的优先级相反。

如果你知道你有布尔参数,你可以使用 NumPy 的按位运算符,但要小心处理括号,就像这样:z = (x > 1) & (x < 2)。NumPy 没有logical_andlogical_or运算符形式是 Python 设计中不幸的结果。

重塑和线性索引:MATLAB 始终允许使用标量或线性索引来访问多维数组,NumPy 则不允许。线性索引在 MATLAB 程序中很常见,例如对矩阵进行find()操作返回它们,而 NumPy 的find()操作行为不同。在转换 MATLAB 代码时,可能需要首先将矩阵重塑为线性序列,进行一些索引操作,然后再重塑回去。由于重塑(通常)生成对存储空间的视图,因此应该可以相当有效地进行此操作。请注意,NumPy 中的 reshape 使用的扫描顺序默认为“C”顺序,而 MATLAB 使用 Fortran 顺序。如果你只是将其转换为线性序列并返回,这并不重要。但是,如果你要从依赖扫描顺序的 MATLAB 代码中转换重塑操作,那么此 MATLAB 代码:z = reshape(x,3,4);应该在 NumPy 中变成z = x.reshape(3,4,order='F').copy()

‘array’或‘matrix’?我应该使用哪一个?

从历史角度来看,NumPy 提供了一个特殊的矩阵类型* np.matrix*,它是 ndarray 的子类,可以进行二进制运算和线性代数运算。你可能会在一些现有代码中看到它的使用,而不是* np.array*。那么,应该使用哪一个?

简短回答

使用数组

  • 支持在 MATLAB 中支持的多维数组代数

  • 它们是 NumPy 的标准向量/矩阵/张量类型。许多 NumPy 函数返回数组而不是矩阵。

  • 在元素级运算和线性代数运算之间存在明显区别。

  • 你可以拥有标准向量或行/列向量。

直到 Python 3.5 之前,使用数组类型的唯一劣势是你必须使用dot而不是*来对两个张量(标量积,矩阵向量乘法等)进行乘法运算。自 Python 3.5 以来,你可以使用矩阵乘法@运算符。

鉴于上述问题,我们打算最终弃用matrix

长回答

NumPy 包含array类和matrix类。array类旨在为许多种数值计算提供通用的 n 维数组,而matrix类旨在特定的线性代数计算。实际上,这两者之间只有少数几个关键的区别。

  • 运算符*@,函数dot()multiply()

    • 对于array*表示逐元素相乘,而**@表示矩阵乘法**;它们有关联的函数multiply()dot()。(在 Python 3.5 之前,@不存在,必须使用dot()进行矩阵乘法)。

    • 对于matrix*表示矩阵乘法,对于逐元素相乘,必须使用multiply()函数。

  • 处理向量(一维数组)

    • 对于array形状为 1xN、Nx1 和 N 的向量是完全不同的。例如A[:,1]返回形状为 N 的一维数组,而不是形状为 Nx1 的二维数组。一维array的转置没有任何效果。

    • 对于matrix一维数组始终被上转换为 1xN 或 Nx1 矩阵(行向量或列向量)。A[:,1]返回形状为 Nx1 的二维矩阵。

  • 处理更高维度数组(ndim > 2)

    • array对象可以有大于 2 的维度

    • matrix对象始终具有确切的两个维度

  • 方便的属性

    • array具有.T 属性,返回数据的转置。

    • matrix还具有.H、.I 和.A 属性,分别返回矩阵的共轭转置、逆矩阵和 asarray()。

  • 方便的构造函数

    • array构造函数以(嵌套)Python 序列作为初始化器。如,array([[1,2,3],[4,5,6]])

    • matrix构造函数另外接受方便的字符串初始化器。如matrix("[1 2 3; 4 5 6]")

使用两者都有利有弊:

  • array

    • :) 逐元素相乘很容易:A*B

    • :( 必须记住,矩阵乘法有自己的操作符@

    • :) 您可以将一维数组视为行向量列向量A @ vv视为列向量,而v @ Av视为行向量。这可以节省您的很多转置输入。

    • :) array是“默认”的 NumPy 类型,因此经过最多测试,并且是第三方使用 NumPy 的代码最有可能返回的类型。

    • :) 可以很好地处理任意维度的数据。

    • :) 如果你熟悉张量代数,:更接近语义。

    • :) 所有操作(*/+- 等)都是逐元素的。

    • :( 来自scipy.sparse的稀疏矩阵与数组的交互性不佳。

  • matrix

    • :\\ 行为更像 MATLAB 的矩阵。

    • <:(最多为二维。要保存三维数据,您需要array或者可能是一个matrix的 Python 列表。

    • <:(最少为二维。不能有向量。它们必须被强制转换为单列或单行矩阵。

    • <:( 由于array在 NumPy 中是默认值,一些函数可能返回一个array,即使你给它们一个matrix作为参数。这不应该发生在 NumPy 函数中(如果发生了,那是一个错误),但基于 NumPy 的第三方代码可能不会像 NumPy 那样遵守类型保留。

    • :) A*B是矩阵乘法,因此看起来就像您在线性代数中编写一样(对于 Python >= 3.5,普通数组使用@操作符具有相同的便利)。

    • <:( 按元素相乘需要调用函数multiply(A,B)

    • <:( 操作符重载的使用有点不合逻辑:*不是按元素运行,但/是。

    • scipy.sparse的交互方式更清晰。

因此,使用array更加明智。的确,我们最终打算废弃matrix

自定义您的环境

在 MATLAB 中,用于自定义环境的主要工具是修改搜索路径,包含您喜欢函数的位置。您可以将这种定制放入 MATLAB 将在启动时运行的启动脚本中。

NumPy,或者更确切地说是 Python,具有类似的功能。

  • 要修改 Python 搜索路径以包括您自己模块的位置,请定义PYTHONPATH环境变量。

  • 要在启动交互式 Python 解释器时执行特定的脚本文件,请定义PYTHONSTARTUP环境变量,其中包含您启动脚本的名称。

与 MATLAB 不同,在 Python 中,您需要首先执行一个‘import’语句来使特定文件中的函数可访问。

例如,您可以创建一个看起来像这样的启动脚本(注意:这只是一个例子,不是“最佳实践”的陈述):

# Make all numpy available via shorter 'np' prefix
import numpy as np
#
# Make the SciPy linear algebra functions available as linalg.func()
# e.g. linalg.lu, linalg.eig (for general l*B@u==A@u solution)
from scipy import linalg
#
# Define a Hermitian function
def hermitian(A, **kwargs):
    return np.conj(A,**kwargs).T
# Make a shortcut for hermitian:
#    hermitian(A) --> H(A)
H = hermitian 

要使用不推荐使用的matrix和其他matlib函数:

# Make all matlib functions accessible at the top level via M.func()
import numpy.matlib as M
# Make some matlib functions accessible directly at the top level via, e.g. rand(3,3)
from numpy.matlib import matrix,rand,zeros,ones,empty,eye 

链接

可以在mathesaurus.sf.net/找到另一个有些过时的 MATLAB/NumPy 交叉参考。

可以在专题软件页面中找到用于科学工作的 Python 的工具的广泛列表。

请参阅Python 软件列表:脚本以获取使用 Python 作为脚本语言的软件列表

MATLAB®和 SimuLink®是 The MathWorks,Inc.的注册商标。

介绍

MATLAB®和 NumPy 有很多共同之处,但 NumPy 是为了与 Python 一起工作而创建的,并不是 MATLAB 的克隆。本指南将帮助 MATLAB 用户开始使用 NumPy。

一些关键区别

大致的等价物

下表列出了一些常见 MATLAB 表达式的大致等价物。这些是相似的表达式,而不是等价物。详情请参见文档。

在下表中,假设你已在 Python 中执行了以下命令:

import numpy as np
from scipy import io, integrate, linalg, signal
from scipy.sparse.linalg import cg, eigs 

还假设下面的附注说明了“矩阵”,即参数为二维实体。

通用目的的等价物

|

for i=1:3
    fprintf('%i\n',i)
end 

|

for i in range(1, 4):
   print(i) 

|

>>  4  ==  4
ans  =  1
>>  4  ==  5
ans  =  0 

|

>>> 4 == 4
True
>>> 4 == 5
False 

|

a=4
if  a==4
  fprintf('a = 4\n')
elseif  a==5
  fprintf('a = 5\n')
end 

|

a = 4
if a == 4:
    print('a = 4')
elif a == 5:
    print('a = 5') 

线性代数的等价操作

|

rng(42,'twister')
rand(3,4) 

|

from numpy.random import default_rng
rng = default_rng(42)
rng.random(3, 4) 

or older version: random.rand((3, 4)) | 用默认的随机数生成器和种子 = 42 生成一个随机的 3x4 数组 |

通用等价物

|

for i=1:3
    fprintf('%i\n',i)
end 

|

for i in range(1, 4):
   print(i) 

|

>>  4  ==  4
ans  =  1
>>  4  ==  5
ans  =  0 

|

>>> 4 == 4
True
>>> 4 == 5
False 

|

a=4
if  a==4
  fprintf('a = 4\n')
elseif  a==5
  fprintf('a = 5\n')
end 

|

a = 4
if a == 4:
    print('a = 4')
elif a == 5:
    print('a = 5') 

同等的线性代数

|

rng(42,'twister')
rand(3,4) 

|

from numpy.random import default_rng
rng = default_rng(42)
rng.random(3, 4) 

或者旧版本:random.rand((3, 4)) | 使用默认的随机数生成器和 seed = 42 生成一个随机的 3x4 数组 |

注释

子矩阵: 可以使用ix_命令和索引列表对子矩阵进行赋值。例如,对于 2D 数组a,可以这样操作:ind=[1, 3]; a[np.ix_(ind, ind)] += 100

帮助: Python 没有直接相当于 MATLAB 中which命令的命令,但helpnumpy.source命令通常会列出函数所在的文件名。Python 还有一个inspect模块(导入import inspect),其中提供了一个getfile函数通常也会起作用。

索引:MATLAB 使用基于一的索引,因此序列的初始元素索引为 1。Python 使用基于零的索引,因此序列的初始元素索引为 0。关于这一点可能会产生混淆和激烈争论,因为每种方式都有其优势和劣势。基于一的索引符合通常的人类语言使用习惯,其中序列的“第一个”元素索引为 1。基于零的索引简化了索引操作。也请参见 Edsger W. Dijkstra 教授的一篇文章

范围:在 MATLAB 中,0:5既可以用作范围字面量,也可以用作‘切片’索引(放在括号内);然而在 Python 中,像0:5这样的构造只能作为切片索引(放在方括号内)使用。因此,为了使 NumPy 可以拥有类似简洁的范围构造机制,特别创建了r_对象。需要注意的是,r_ 不像函数或构造函数一样被调用,而是使用方括号进行索引,这样可以在参数中使用 Python 的切片语法。

逻辑操作:在 NumPy 中,&| 是按位与/或运算,而在 MATLAB 中,& 和 | 分别是逻辑与/或运算。这两者看起来可能是一样的,但实际上存在重要的区别。如果你曾经使用过 MATLAB 的 &| 运算符,那么在 NumPy 中应该使用对应的 ufuncs logical_and/logical_or。MATLAB 和 NumPy 的 &| 操作符之间的显著区别包括:

  • 非逻辑 {0,1} 输入:NumPy 的输出是输入的按位与。MATLAB 将任何非零值视为 1,并返回逻辑与。例如在 NumPy 中 (3 & 4) 的结果是 0,而在 MATLAB 中 34 都被视为逻辑 true,因此 (3 & 4) 的结果是 1

  • 优先级:NumPy 的 & 运算符的优先级高于诸如 <> 的逻辑运算符;而 MATLAB 则相反。

如果你知道参数是布尔值,你可以使用 NumPy 的按位运算符,但是在使用括号时要小心,就像这样:z = (x > 1) & (x < 2)。NumPy 没有形式上的 logical_andlogical_or 运算符是 Python 设计的一个不幸的结果。

重塑和线性索引: MATLAB 始终允许使用标量或线性索引访问多维数组,而 NumPy 则不是。线性索引在 MATLAB 程序中很常见,例如,对矩阵进行find()返回它们,而 NumPy 的find()行为有所不同。在转换 MATLAB 代码时,可能需要首先将矩阵重塑为线性序列,执行一些索引操作,然后再进行重塑。由于 reshape(通常)提供对相同存储的视图,因此应该可以相当高效地完成此操作。请注意,NumPy 中 reshape 的扫描顺序默认为‘C’顺序,而 MATLAB 使用 Fortran 顺序。如果你仅仅是将其转换为线性序列然后再转换回来,这并不重要。但如果你正在从依赖扫描顺序的 MATLAB 代码转换 reshape,那么此 MATLAB 代码:z = reshape(x,3,4);应该在 NumPy 中变为z = x.reshape(3,4,order='F').copy()

‘array’或‘matrix’?我应该使用哪个?

从历史上看,NumPy 提供了一种特殊的矩阵类型,np.matrix,它是 ndarray 的一个子类,使二进制操作变成线性代数操作。你可能会在一些现有代码中看到它,而不是np.array。那么,应该使用哪一个?

简短答案

使用 arrays

  • 它们支持 MATLAB 中支持的多维数组代数运算

  • 它们是 NumPy 的标准向量/矩阵/张量类型。许多 NumPy 函数返回数组,而不是矩阵。

  • 元素操作与线性代数操作有明显区别。

  • 如果你喜欢,可以使用标准向量或行/列向量。

直到 Python 3.5,使用array类型的唯一缺点是你必须使用dot而不是*来乘法(缩减)两个张量(数量积,矩阵向量乘法等)。从 Python 3.5 开始,你可以使用矩阵乘法@运算符。

鉴于上述,我们计划最终弃用matrix

长答案

NumPy 包含array类和matrix类。array类旨在成为通用的多维数组,用于各种数值计算,而matrix旨在特定地促进线性代数计算。在实践中,这两者之间只有少数几个关键差异。

  • 运算符*@,函数dot()multiply()

    • 对于array*表示逐元素相乘,而**@表示矩阵乘法**;它们有相关的函数multiply()dot()。(Python 3.5 之前,@不存在,人们必须使用dot()进行矩阵乘法)。

    • 对于matrix*表示矩阵乘法,对于逐元素乘法,人们必须使用multiply()函数。

  • 向量(一维数组)的处理

    • 对于array来说,向量形状 1xN,Nx1 和 N 是完全不同的事情。像A[:,1]这样的操作返回形状为 N 的一维数组,而不是形状为 Nx1 的二维数组。在一维array上进行转置没有任何效果。

    • 对于matrix一维数组总是转换为 1xN 或 Nx1 矩阵(行向量或列向量)。A[:,1]返回形状为 Nx1 的二维矩阵。

  • 处理更高维数组(ndim > 2)

    • array对象可以具有大于 2 的维数

    • matrix对象始终具有确切的两个维度

  • 便利属性

    • array具有.T 属性,返回数据的转置。

    • matrix还具有.H, .I 和 .A 属性,分别返回矩阵的共轭转置、逆和asarray()

  • 便利构造函数

    • array构造函数接受(嵌套的)Python 序列作为初始化器。如array([[1,2,3],[4,5,6]])

    • matrix构造函数另外接受方便的字符串初始化器。如 matrix("[1 2 3; 4 5 6]").

使用两者都有利弊:

  • array

    • :) 逐元素乘法很容易:A*B

    • :( 您必须记住,矩阵乘法有自己的运算符@

    • :) 您可以将一维数组视为行向量列向量A @ vv视为列向量,而v @ Av视为行向量。这样可以避免您输入许多转置。

    • :) array是 NumPy 的“默认”类型,因此它得到了最多的测试,并且最有可能被使用 NumPy 的第三方代码返回。

    • :) 它在处理任意维度的数据时非常方便。

    • :) 如果您熟悉张量代数的话,语义上更接近。

    • :) 所有操作(*/+- 等)都是逐元素的。

    • :( scipy.sparse 中的稀疏矩阵与数组的交互不太好。

  • matrix

    • :\\ 行为更像 MATLAB 矩阵。

    • <:( 三维数据需要使用array,或者可能是matrix的 Python 列表。

    • <:( 两维矩阵的最小值。不能有向量。它们必须被转换为单列或单行矩阵。

    • <:( 由于在 NumPy 中array是默认的,一些函数可能返回array,即使您给它们一个matrix作为参数。这不应该发生在 NumPy 函数中(如果发生了,那是个错误),但基于 NumPy 的第三方代码可能不像 NumPy 那样尊重类型保留。

    • :) A*B是矩阵乘法,所以它看起来就像您在线性代数中写的(对于 Python >= 3.5,普通数组使用@运算符也有同样的便利)。

    • <:( 逐元素乘法需要调用一个函数,multiply(A,B)

    • <:( 使用运算符重载有点不合逻辑:*不逐元素工作,但/却是。

    • scipy.sparse的交互更清晰。

因此,更建议使用array。实际上,我们最终打算停用matrix

简短答案

使用数组

  • 支持在 MATLAB 中支持的多维数组代数

  • 它们是 NumPy 的标准向量/矩阵/张量类型。许多 NumPy 函数返回数组,而不是矩阵。

  • 元素级操作和线性代数操作之间有明显的区别。

  • 如果需要,你可以使用标准向量或行向量/列向量。

在 Python 3.5 之前,使用 array 类型的唯一不利之处是必须使用 dot 而不是 * 进行乘法(缩减)两个张量(标量积、矩阵向量乘法等)。自从 Python 3.5 以来,可以使用矩阵乘法 @ 运算符。

根据上面的内容,我们打算最终废弃 matrix

较长的回答

NumPy 中包含 array 类和 matrix 类。array 类旨在成为一种通用的 n 维数组,适用于各种数值计算,而 matrix 则旨在专门用于线性代数计算。实际上,这两者之间只有一小部分关键差异。

  • 运算符 *@,函数 dot()multiply()

    • 对于 array* 表示逐元素乘法,而 @ 表示矩阵乘法;它们分别对应的函数是 multiply()dot()。(在 Python 3.5 之前,@ 不存在,必须使用 dot() 进行矩阵乘法)。

    • 对于 matrix* 表示矩阵乘法,对于逐元素乘法必须使用 multiply() 函数。

  • 向量(一维数组)的处理

    • 对于 array,向量的形状 1xN、Nx1 和 N 是不同的概念。例如,A[:,1] 返回形状为 N 的一维数组,而不是形状为 Nx1 的二维数组。对一维 array 进行转置没有任何变化。

    • 对于 matrix,一维数组总是被转换为 1xN 或 Nx1 的矩阵(行向量或列向量)。A[:,1] 返回形状为 Nx1 的二维矩阵。

  • 高维数组(ndim > 2)的处理

    • array 对象可以具有大于 2 的维数

    • matrix 对象始终仅有两个维度。

  • 方便的属性

    • array 具有 .T 属性,可以返回数据的转置。

    • matrix 还有 .H.I.A 属性,它们分别返回矩阵的共轭转置、逆和 asarray()

  • 方便的构造函数

    • array 构造函数接受嵌套的 Python 序列作为初始化参数。例如,array([[1,2,3],[4,5,6]])

    • matrix 构造函数还支持方便的字符串初始化。例如,matrix("[1 2 3; 4 5 6]")

使用它们都有利有弊:

  • array

    • :) 逐元素乘法很简单:A*B

    • :( 你必须记住矩阵乘法有自己的运算符 @

    • :) 你可以将一维数组当作行向量列向量处理。A @ vv 视为列向量,而 v @ Av 视为行向量。这样可以减少输入转置的次数。

    • :) array 是“默认”的 NumPy 类型,因此它受到最多的测试,并且是第三方使用 NumPy 的代码可能返回的类型。

    • :) 在处理任意维数的数据时都非常便捷。

    • :) 与张量代数更接近的语义,如果你熟悉的话。

    • :) 所有操作(*/+- 等)都是逐个元素进行的。

    • :( 使用 scipy.sparse 的稀疏矩阵与数组的交互效果不太好。

  • 矩阵

    • :\\ 行为更像 MATLAB 矩阵。

    • <:( 二维矩阵的最大值。要保存三维数据,你需要使用 array 或者可能是一个 matrix 的 Python 列表。

    • <:( 二维矩阵的最小值。你不能有向量。它们必须被转换为单列矩阵或单行矩阵。

    • <:( 由于 array 是 NumPy 的默认选项,所以一些函数可能会返回一个 array,即使你将 matrix 作为参数传递给它们也会如此。 NumPy 函数不应该出现这种情况(如果出现了就是一个 bug),但基于 NumPy 的第三方代码可能不像 NumPy 那样保留类型信息。

    • :) A*B 是矩阵乘法,因此它的写法与线性代数中一样(对于 Python >= 3.5,普通数组可以使用 @ 操作符达到相同的方便性)。

    • <:( 对元素进行逐个乘法操作需要调用函数 multiply(A, B)

    • <:( 操作符重载的使用有点不合逻辑:* 不对元素进行操作,但 / 是对每个元素进行操作的。

    • scipy.sparse 的交互更清晰。

因此,更建议使用 array。事实上,我们打算最终废弃 matrix

定制环境

在 MATLAB 中,定制环境的主要工具是修改搜索路径以包含你喜欢的函数的位置。你可以将这样的定制放入 MATLAB 在启动时运行的启动脚本中。

NumPy,或者更准确地说是 Python,有类似的功能。

  • 若要修改 Python 搜索路径以包含自己模块的位置,请定义 PYTHONPATH 环境变量。

  • 当启动交互式 Python 解释器时,若要执行特定的脚本文件,请定义 PYTHONSTARTUP 环境变量,其包含你启动脚本的名称。

与 MATLAB 不同,你需要先使用 ‘import’ 语句使特定文件中的函数可访问,然后才能立即调用。

例如,你可以创建一个启动脚本,内容如下(注意:此处仅为示例,并不是“最佳实践”的陈述):

# Make all numpy available via shorter 'np' prefix
import numpy as np
#
# Make the SciPy linear algebra functions available as linalg.func()
# e.g. linalg.lu, linalg.eig (for general l*B@u==A@u solution)
from scipy import linalg
#
# Define a Hermitian function
def hermitian(A, **kwargs):
    return np.conj(A,**kwargs).T
# Make a shortcut for hermitian:
#    hermitian(A) --> H(A)
H = hermitian 

要使用已弃用的 matrix 和其他 matlib 函数:

# Make all matlib functions accessible at the top level via M.func()
import numpy.matlib as M
# Make some matlib functions accessible directly at the top level via, e.g. rand(3,3)
from numpy.matlib import matrix,rand,zeros,ones,empty,eye 

链接

mathesaurus.sf.net/ 可以找到另一个相对陈旧的 MATLAB/NumPy 相关信息。

可以在 主题软件页面 中找到用于使用 Python 进行科学工作的工具的详尽列表。

请查看 Python 软件列表:脚本语言 获取使用 Python 作为脚本语言的软件列表。

MATLAB® 和 SimuLink® 是 The MathWorks, Inc. 的注册商标。

NumPy 特性

一系列与内置 NumPy 功能相关的笔记。

  • n 维数组上的线性代数

  • 保存和分享您的 NumPy 数组

  • 掩码数组

NumPy 如何操作

这些文档旨在提供使用 NumPy 执行常见任务的方法。有关包中包含的函数和类的详细参考文档,请参见 API 参考。

  • 如何编写 NumPy 操作指南

  • 读取和写入文件

  • 如何索引 ndarrays

  • 验证 NumPy 中的错误和 bug 修复

  • 如何创建具有等距数值的数组

高级用法和互操作性

从源码编译

在本地计算机上构建可以完全控制构建选项。如果你是一名熟悉使用命令行的 MacOS 或 Linux 用户,则可以继续按照下面的说明构建 NumPy。

注意

如果要构建用于开发目的的 NumPy,请参阅 Setting up and using your development environment 获取更多信息。

先决条件

编译 NumPy 需要已安装以下软件:

  1. Python 3.9.x 或更高版本

    请注意,还需要安装 Python 开发头文件,例如,在 Debian/Ubuntu 上需要同时安装 python3python3-dev。在 Windows 和 macOS 上通常不会出现此问题。

  2. 编译器

    NumPy 的很大一部分是用 C 和 C++ 编写的。你需要一个符合 C99 标准的 C 编译器,以及一个符合 C++17 标准的 C++ 编译器。

    尽管构建 NumPy 不需要 FORTRAN 77 编译器,在运行 numpy.f2py 测试时需要它。如果未自动检测到编译器,则会跳过这些测试。

    注意,NumPy 主要是使用 GNU 编译器进行开发,并在 MSVC 和 Clang 编译器上进行测试。像 Intel、Absoft、Sun、NAG、Compaq、Vast、Portland、Lahey、HP、IBM 等其他供应商的编译器仅以社区反馈的形式提供支持,并不保证可以直接使用。推荐使用 GCC 6.5(或更高版本)编译器。在 ARM64(aarch64)上推荐使用 GCC 8.x(或更高版本)。

  3. 线性代数库

    NumPy 不需要安装任何外部线性代数库。然而,如果这些库可用,NumPy 的设置脚本可以检测到并用于构建。可以使用多种不同的 LAPACK 库设置,包括优化的 LAPACK 库,如 OpenBLAS 或 MKL。这些库的选择和位置以及包含路径和其他构建选项可以在 .pc 文件中指定,如 BLAS 和 LAPACK 中所述。

  4. Cython

    构建 NumPy 需要一个较新版本的 Cython。

  5. NumPy 源代码

    按照 Contributing to NumPy 中的说明,克隆仓库。

注意

从版本 1.26 开始,NumPy 将采用 Meson 作为构建系统(详见 Status of numpy.distutils and migration advice 和 Understanding Meson)。

基本安装

要从本地源代码构建和安装 NumPy,请运行:

pip install . 

这将安装所有构建依赖项,并使用 Meson 编译和安装 NumPy 的 C 扩展和 Python 模块。如果需要对构建选项和命令有更多控制,请参阅以下各节。

要执行可以从源文件夹运行的就地构建,请运行:

pip install -r build_requirements.txt
pip install -e . --no-build-isolation 

注意:有关在 NumPy 本身上进行开发工作的构建说明,请参阅 设置和使用开发环境。

使用 Meson 进行高级构建

Meson 支持标准环境变量CCCXXFC来选择特定的 C、C++和/或 Fortran 编译器。这些环境变量在Meson 文档中的参考表中有文档说明。

请注意,环境变量仅在干净构建时应用,因为它们影响配置阶段(即 meson setup)。增量重建不会对环境变量的更改做出反应-您必须运行git clean -xdf并进行完全重建,或运行meson setup --reconfigure

更多选项,包括选择编译器、设置自定义编译器标志和控制并行性,请参阅编译器选择和自定义构建(来自 SciPy 文档)和Meson FAQ

测试

确保测试你的构建。为了确保一切都正常,查看所有测试是否通过。

测试套件需要额外的依赖项,可以通过以下方式轻松安装:

python -m pip install -r test_requirements.txt 

运行完整的测试套件:

cd ..  # avoid picking up the source tree
pytest --pyargs numpy 

有关测试的详细信息,请参阅测试构建。

加速 BLAS/LAPACK 库

NumPy 搜索优化的线性代数库,例如 BLAS 和 LAPACK。搜索这些库有特定的顺序,如下所述和meson_options.txt文件中描述。

交叉编译

对于交叉编译指令,请参阅交叉编译和 Meson 文档。

先决条件

构建 NumPy 需要安装以下软件:

  1. Python 3.9.x 或更新版本

    请注意,还需要安装 Python 开发头文件,例如,在 Debian/Ubuntu 上需要同时安装python3python3-dev。在 Windows 和 macOS 上,这通常不是问题。

  2. 编译器

    NumPy 的大部分代码是用 C 和 C++编写的。您需要一个符合 C99 标准的 C 编译器,以及一个符合 C++17 标准的 C++编译器。

    虽然构建 NumPy 不需要 FORTRAN 77 编译器,但运行numpy.f2py测试需要。如果编译器没有被自动检测到,则这些测试会被跳过。

    请注意,NumPy 主要是使用 GNU 编译器开发并在 MSVC 和 Clang 编译器上进行测试。来自其他供应商的编译器(如 Intel、Absoft、Sun、NAG、Compaq、Vast、Portland、Lahey、HP、IBM)仅通过社区反馈的形式支持,并且可能无法直接使用。推荐使用 GCC 6.5(及更高版本)编译器。在 ARM64(aarch64)上,推荐使用 GCC 8.x(及更高版本)。

  3. 线性代数库

    NumPy 不需要安装任何外部线性代数库。但是,如果这些库可用,NumPy 的设置脚本可以检测到并用于构建。可以使用多种不同的 LAPACK 库设置,包括经过优化的 LAPACK 库,如 OpenBLAS 或 MKL。这些库的选择和位置以及包含路径等构建选项可以在 .pc 文件中指定,如 BLAS 和 LAPACK 中所述。

  4. Cython

    要构建 NumPy,您需要一个较新版本的 Cython。

  5. NumPy 源代码

    按照 为 NumPy 做出贡献 中的说明克隆存储库。

注意

从版本 1.26 开始,NumPy 将采用 Meson 作为其构建系统(请参阅 numpy.distutils 的状态和迁移建议 和 理解 Meson 了解更多细节)。

基本安装

要从源代码的本地副本构建并安装 NumPy,请运行:

pip install . 

这将安装所有构建依赖项并使用 Meson 编译并安装 NumPy 的 C 扩展和 Python 模块。如果您需要更多控制构建选项和命令,请参见以下章节。

要执行可以从源文件夹运行的原地构建,请运行:

pip install -r build_requirements.txt
pip install -e . --no-build-isolation 

注意:有关在 NumPy 上进行开发工作的构建说明,请参阅 配置和使用开发环境。

使用 Meson 进行高级构建

Meson 支持标准环境变量 CCCXXFC 以选择特定的 C、C++ 和/或 Fortran 编译器。这些环境变量在 Meson 文档中的参考表中 有文档记录。

请注意,环境变量只会在干净构建时生效,因为它们会影响配置阶段(即,meson 设置)。增量重建不会对环境变量的更改做出反应-您必须运行 git clean -xdf 并进行完整重建,或运行 meson setup --reconfigure

更多选项包括选择编译器、设置自定义编译器标志和控制并行性,请参阅编译器选择和自定义构建(来自 SciPy 文档)和the Meson FAQ

使用 Meson 进行高级构建

Meson 支持标准环境变量CCCXXFC来选择特定的 C、C++和/或 Fortran 编译器。这些环境变量在Meson 文档中的参考表中有文档。

请注意,只有在干净的构建过程中,环境变量才会得到应用,因为它们影响配置阶段(即 meson setup)。增量重新构建不会对环境变量的更改作出反应-您必须运行git clean -xdf并进行全面重建,或者运行meson setup --reconfigure

更多选项包括选择编译器、设置自定义编译器标志和控制并行性,请参阅编译器选择和自定义构建(来自 SciPy 文档)和the Meson FAQ

测试

确保测试您的构建。为了确保一切正常,请检查所有测试是否通过。

测试套件需要额外的依赖项,可以通过以下命令轻松安装:

python -m pip install -r test_requirements.txt 

运行完整的测试套件:

cd ..  # avoid picking up the source tree
pytest --pyargs numpy 

有关测试的详细信息,请参阅测试构建。

加速 BLAS/LAPACK 库

NumPy 搜索优化的线性代数库,如 BLAS 和 LAPACK。有特定的搜索这些库的顺序,如下所述和meson_options.txt文件中描述的。

交叉编译

如需交叉编译说明,请参阅交叉编译和 Meson 文档。

使用 NumPy C-API

  • 如何扩展 NumPy

    • 编写扩展模块

    • 必需子程序

    • 定义函数

      • 无关键字参数的函数

      • 带关键字参数的函数

      • 引用计数

    • 处理数组对象

      • 转换任意序列对象

      • 创建全新的 ndarray

      • 访问 ndarray 内存和访问 ndarray 元素

    • 示例

  • 使用 Python 作为粘合剂

    • 从 Python 调用其他编译库

    • 手动生成的包装器

    • f2py

    • Cython

      • Cython 中的复数加法

      • Cython 中的图像滤波

      • 结论

    • ctypes

      • 拥有共享库

      • 加载共享库

      • 转换参数

      • 调用函数

        • ndpointer
      • 完整示例

      • 结论

    • 您可能会发现有用的其他工具

      • SWIG

      • SIP

      • Boost Python

      • PyFort

  • 编写自己的 ufunc

    • 创建新的通用函数

    • 示例非通用函数扩展

    • 带一种数据类型的 NumPy ufunc 示例

    • 带有多种数据类型的 NumPy ufunc 示例

    • 具有多个参数/返回值的示例 NumPy ufunc

    • 具有结构化数组数据类型参数的示例 NumPy ufunc

  • 超越基础知识

    • 在数组中迭代元素

      • 基本迭代

      • 在除了一个轴之外的所有轴上进行迭代

      • 在多个数组上进行迭代

      • 在多个数组上进行广播

    • 用户定义数据类型

      • 添加新数据类型

      • 注册强制类型转换函数

      • 注册强制类型转换规则

      • 注册 ufunc 循环

    • 在 C 中对 ndarray 进行子类型化

      • 创建子类型

      • ndarray 子类型的特定特征

        • array_finalize 方法

          • ndarray.__array_finalize__
        • array_priority 属性

          • ndarray.__array_priority__
        • array_wrap 方法

          • ndarray.__array_wrap__

F2PY 用户指南和参考手册

F2PY - Fortran 到 Python 接口生成器 - 实用程序的目的是提供 Python 与 Fortran 之间的连接。F2PY 是NumPy (numpy.f2py)的一部分,也作为一个独立的命令行工具可用。

F2PY 有助于创建/构建使其成为可能的 Python C/API 扩展模块

  • 用于调用 Fortran 77/90/95 外部子例程以及 Fortran 90/95 模块子例程以及 C 函数;

  • 用于访问 Fortran 77 COMMON块和 Fortran 90/95 模块数据,包括可分配数组

从 Python。

F2PY 可以作为命令行工具f2py或作为一个 Python 模块numpy.f2py来使用。虽然我们尝试将命令行工具作为 numpy 设置的一部分提供,但像 Windows 这样的某些平台很难可靠地将可执行文件放在PATH上。如果您的系统中没有f2py命令可用,您可能需要将其作为模块运行:

python -m numpy.f2py 

如果您运行f2py而没有参数,并且最后一行的numpy 版本与从python -m numpy.f2py打印的 NumPy 版本匹配,则可以使用较短的版本。如果不是这样,或者无法运行f2py,则应该将本指南中提到的所有对f2py的调用替换为较长的版本。

  • 包装的三种方式 - 入门指南

    • 快速的方式

    • 明智的做法

    • 快速又聪明的方式

  • F2PY 用户指南

    • 包装的三种方式 - 入门指南

      • 快速的方式

      • 明智的做法

      • 快速又聪明的方式

    • 使用 F2PY

      • 使用f2py作为命令行工具

      • Python 模块numpy.f2py

      • 自动生成扩展模块

    • F2PY 示例

      • F2PY 演练:一个基本的扩展模块

      • 一个过滤示例

      • depends关键字示例

      • 阅读更多

  • F2PY 参考手册

    • 签名文件

      • 签名文件语法
    • 在 Python 中使用 F2PY 绑定

      • Fortran 类型对象

      • 标量参数

      • 字符串参数

      • 数组参数

      • 回调参数

      • 公共块

      • Fortran 90 模块数据

      • 可分配数组

    • F2PY 和构建系统

      • 基本概念

      • 构建系统

    • 高级 F2PY 使用情况

      • 向 F2PY 生成的模块添加用户定义函数

      • 添加用户定义变量

      • 处理 KIND 规范

      • 字符字符串

    • F2PY 测试套件

      • 添加一个测试
  • 使用 F2PY

    • f2py 作为命令行工具使用

      • 1. 签名文件生成

      • 2. 扩展模块构建

      • 3. 构建一个模块

      • 其他选项

    • Python 模块 numpy.f2py

      • compile

      • get_include

      • run_main

    • 自动生成扩展模块

  • 在 Python 中使用 F2PY 绑定

    • Fortran 类型对象

    • 标量参数

    • 字符串参数

    • 数组参数

    • 回调参数

      • 解析回调函数的参数
    • 公共块

    • Fortran 90 模块数据

    • 可分配数组

  • 签名文件

    • 签名文件语法

      • Python 模块块

      • Fortran/C 例程签名

      • 类型声明

      • 语句

      • 属性

      • 扩展

      • 扩展的字符选择器

  • F2PY 和构建系统

    • 基本概念

    • 构建系统

      • 通过 numpy.distutils 使用

      • 通过 meson 使用

      • 通过 cmake 使用

      • 通过 scikit-build 使用

  • 高级 F2PY 使用情况

    • 向 F2PY 生成的模块添加用户定义函数

    • 添加用户定义变量

    • 处理 KIND 指定符

    • 字符字符串

      • 假定长度的字符字符串
  • F2PY 和 Windows

    • 概述

    • 基准线

    • Powershell 和 MSVC

    • Windows 商店 Python 路径

      • F2PY 和 Windows Intel Fortran

      • F2PY 和 MSYS2 上的 Windows

      • F2PY 和 Conda on Windows

      • F2PY 和 PGI Fortran on Windows

开发人员的底层文档

这些文档旨在深入了解 NumPy,面向开发人员。

  • NumPy 数组的内部组织

  • NumPy C 代码解释

  • 内存对齐

  • 字节交换

  • 编写自定义数组容器

  • 子类化 ndarray

与 NumPy 的互操作性

NumPy 的 ndarray 对象提供了对数组结构化数据进行操作的高级 API,以及基于 分块内存中存储 的 API 的具体实现。虽然这个 API 功能强大且相当通用,但它的具体实现有限制。随着数据集的增长和 NumPy 在各种新环境和架构中的使用,有些情况下分块内存中存储策略不适用,这导致不同的库为其自己的用途重新实现了这个 API。这包括 GPU 数组 (CuPy)、稀疏数组 (scipy.sparsePyData/Sparse) 和并行数组 (Dask 数组),以及深度学习框架中类似 NumPy 的实现,如 TensorFlowPyTorch。同样,还有许多项目建立在 NumPy API 之上,用于标记和索引数组 (XArray)、自动微分 (JAX)、遮罩数组 (numpy.ma)、物理单位 (astropy.unitspintunyt) 等等,这些项目在 NumPy API 的基础上添加了额外的功能。

然而,用户仍然希望使用熟悉的 NumPy API 和最小(理想情况下为零)的移植开销重新使用现有代码来处理这些数组。考虑到这一目标,为具有与 NumPy 匹配的高级 API 的多维数组实现定义了各种协议。

广义上来说,用于与 NumPy 互操作的特性分为三组:

  1. 将外部对象转换为 ndarray 的方法;

  2. 将执行延迟从 NumPy 函数转移到另一个数组库的方法;

  3. 使用 NumPy 函数并返回外部对象实例的方法。

我们在下面描述这些特性。

1. 在 NumPy 中使用任意对象

NumPy API 的第一组互操作特性允许在可能的情况下将外部对象视为 NumPy 数组。当 NumPy 函数遇到外部对象时,它们会依次尝试:

  1. 缓冲区协议,在 Python C-API 文档 中描述。

  2. __array_interface__ 协议,描述在 此页面 中。作为 Python 缓冲区协议的前身,它定义了一种从其他 C 扩展中访问 NumPy 数组内容的方法。

  3. __array__() 方法,用于要求任意对象将自身转换为数组。

对于缓冲区和__array_interface__协议,对象描述其内存布局,NumPy 会完成其他一切(如果可能的话,就是零拷贝)。如果这不可能,那么对象本身负责从__array__()返回一个ndarray

DLPack是用于以一种语言和设备不可知的方式将外部对象转换为 NumPy 数组的另一种协议。NumPy 不会使用 DLPack 隐式地将对象转换为 ndarrays。它提供了函数numpy.from_dlpack,该函数接受任何实现__dlpack__方法的对象,并输出一个 NumPy ndarray(通常是输入对象的数据缓冲区的视图)。DLPack 的 Python 规范页面详细解释了__dlpack__协议。

数组接口协议

数组接口协议定义了一种让类似数组对象重新使用彼此的数据缓冲区的方式。其实现依赖于以下属性或方法的存在:

  • __array_interface__:包含数组样对象的形状、元素类型,可选的数据缓冲区地址和步长的 Python 字典;

  • __array__(): 返回数组样对象的 NumPy ndarray 视图的方法;

可以直接检查__array_interface__属性:

>>> import numpy as np
>>> x = np.array([1, 2, 5.0, 8])
>>> x.__array_interface__
{'data': (94708397920832, False), 'strides': None, 'descr': [('', '<f8')], 'typestr': '<f8', 'shape': (4,), 'version': 3} 

__array_interface__属性还可以用于就地操纵对象数据:

>>> class wrapper():
...     pass
...
>>> arr = np.array([1, 2, 3, 4])
>>> buf = arr.__array_interface__
>>> buf
{'data': (140497590272032, False), 'strides': None, 'descr': [('', '<i8')], 'typestr': '<i8', 'shape': (4,), 'version': 3}
>>> buf['shape'] = (2, 2)
>>> w = wrapper()
>>> w.__array_interface__ = buf
>>> new_arr = np.array(w, copy=False)
>>> new_arr
array([[1, 2],
 [3, 4]]) 

我们可以检查arrnew_arr是否共享相同的数据缓冲区:

>>> new_arr[0, 0] = 1000
>>> new_arr
array([[1000,    2],
 [   3,    4]])
>>> arr
array([1000, 2, 3, 4]) 

__array__()方法

__array__()方法确保任何类似于 NumPy 的对象(数组,任何公开数组接口的对象,其__array__()方法返回数组或任何嵌套序列的对象)实现它都可以用作 NumPy 数组。如果可能,这意味着使用__array__()来创建数组样对象的 NumPy ndarray 视图。否则,这将复制数据到一个新的 ndarray 对象中。这并不是最佳选择,因为强制将数组强制转换为 ndarrays 可能会导致性能问题,或者需要复制和丢失元数据,原始对象以及原始对象可能具有的任何属性/行为都会丢失。

要查看包括使用__array__()的自定义数组实现的示例,请参见编写自定义数组容器。

DLPack 协议

DLPack协议定义了跨 strided n 维数组对象的内存布局。它提供以下语法以进行数据交换:

  1. 一个numpy.from_dlpack函数,它接受带有__dlpack__方法的(数组)对象,并使用该方法来构建包含来自x的数据的新数组。

  2. 数组对象上的__dlpack__(self, stream=None)__dlpack_device__方法,它们将在from_dlpack中调用,以查询数组所在的设备(在多个 GPU 的情况下可能需要传入正确的流),以及访问数据。

与缓冲区协议不同,DLPack 允许交换包含非 CPU 设备(例如 Vulkan 或 GPU)上数据的数组。由于 NumPy 仅支持 CPU,它只能转换数据存在于 CPU 上的对象。但其他库,如PyTorchCuPy,可能使用该协议在 GPU 上交换数据。

2. 在不转换的情况下操作外部对象

NumPy API 定义的第二组方法允许我们将执行从 NumPy 函数延迟到另一个数组库。

考虑以下函数。

>>> import numpy as np
>>> def f(x):
...     return np.mean(np.exp(x)) 

请注意,np.exp是一个 ufunc,这意味着它以逐元素的方式作用于 ndarrays。另一方面,np.mean沿着数组的一个轴进行操作。

我们可以将f直接应用于一个 NumPy ndarray 对象:

>>> x = np.array([1, 2, 3, 4])
>>> f(x)
21.1977562209304 

我们希望这个函数能够在任何类似于 NumPy 的数组对象上同样有效。

NumPy 允许类通过以下接口指示它希望以自定义方式进行计算:

  • __array_ufunc__: 允许第三方对象支持和覆盖 ufuncs。

  • __array_function__: 用于覆盖通用函数中不涵盖的 NumPy 功能的默认操作。

只要外部对象实现了__array_ufunc____array_function__协议,就可以在它们上操作而无需进行显式转换。

__array_ufunc__ 协议

通用函数(或简写为 ufunc)是一个对函数进行“矢量化”封装的函数,它接受固定数量的特定输入并产生固定数量的特定输出。如果非 ndarray 对象的输入定义了__array_ufunc__方法,则控制完全传递给该函数,即 ufunc 将被覆盖。在该(非 ndarray)对象上定义的__array_ufunc__方法可以访问 NumPy ufunc。由于 ufunc 具有明确定义的结构,外部__array_ufunc__方法可能依赖 ufunc 属性,如.at().reduce()等。

子类可以通过覆盖默认的ndarray.__array_ufunc__方法来覆盖在其上执行 NumPy ufuncs 时发生的情况。这个方法将代替 ufunc 的执行,并且应该返回操作的结果,或者如果请求的操作未实现,则返回NotImplemented

__array_function__ 协议

为了足够覆盖 NumPy API 以支持下游项目,需要超越 __array_ufunc__ 并实现一个协议,允许 NumPy 函数的参数控制并将执行转移到另一个函数(例如 GPU 或并行实现),以安全和一致的方式跨项目进行。

__array_function__的语义与__array_ufunc__非常相似,只是操作由任意可调用对象指定,而不是由 ufunc 实例和方法指定。更多细节,请参见NEP 18 — NumPy 高级数组函数的调度机制,其中包含 NumPy Enhancement Proposals。"

3. 返回外部对象

第三种特性集旨在使用 NumPy 函数实现,然后将返回值转换为外部对象的实例。__array_finalize____array_wrap__ 方法在幕后起作用,以确保可以根据需要指定 NumPy 函数的返回类型。

__array_finalize__ 方法是 NumPy 提供的机制,允许子类处理新实例被创建的各种方式。每当系统从 ndarray 的子类(子类型)内部分配新数组时,都会调用此方法。它可以用于在构建后更改属性,或从“父类”更新元信息。

__array_wrap__ 方法“包装了行动”,意思是允许任何对象(如用户定义的函数)设置其返回值的类型并更新属性和元数据。这可以被视为__array__方法的相反。在每个实现__array_wrap__的对象的最高数组优先级或指定的输出对象之后,将对输入对象调用此方法。 __array_priority__属性用于确定在返回对象的 Python 类型存在多种可能性的情况下要返回什么类型的对象。例如,子类可以选择使用此方法将输出数组转换为子类的实例,并在将数组返回给用户之前更新元数据。

有关这些方法的更多信息,请参阅 ndarray 子类化 和 ndarray 子类型的特定特性。

互操作性示例

示例:Pandas Series 对象

考虑以下内容:

>>> import pandas as pd
>>> ser = pd.Series([1, 2, 3, 4])
>>> type(ser)
pandas.core.series.Series 

现在,ser 不是一个 ndarray,但因为它实现了 array_ufunc 协议,我们可以将 ufunc 应用于它,好像它是一个 ndarray 一样:

>>> np.exp(ser)
 0     2.718282
 1     7.389056
 2    20.085537
 3    54.598150
 dtype: float64
>>> np.sin(ser)
 0    0.841471
 1    0.909297
 2    0.141120
 3   -0.756802
 dtype: float64 

我们甚至可以与其他 ndarray 执行操作:

>>> np.add(ser, np.array([5, 6, 7, 8]))
 0     6
 1     8
 2    10
 3    12
 dtype: int64
>>> f(ser)
21.1977562209304
>>> result = ser.__array__()
>>> type(result)
numpy.ndarray 

示例:PyTorch 张量

PyTorch 是一个针对使用 GPU 和 CPU 进行深度学习的优化张量库。PyTorch 数组通常被称为 张量。张量类似于 NumPy 的 ndarrays,只是张量可以在 GPU 或其他硬件加速器上运行。事实上,张量和 NumPy 数组通常可以共享相同的底层内存,消除了复制数据的需要。

>>> import torch
>>> data = [[1, 2],[3, 4]]
>>> x_np = np.array(data)
>>> x_tensor = torch.tensor(data) 

请注意 x_npx_tensor 是不同种类的对象:

>>> x_np
array([[1, 2],
 [3, 4]])
>>> x_tensor
tensor([[1, 2],
 [3, 4]]) 

然而,我们可以将 PyTorch 张量视为 NumPy 数组,而无需显式转换:

>>> np.exp(x_tensor)
tensor([[ 2.7183,  7.3891],
 [20.0855, 54.5982]], dtype=torch.float64) 

此外,请注意该函数的返回类型与初始数据类型兼容。

警告

尽管将 ndarrays 和张量混合使用可能很方便,但不建议这样做。它对于非 CPU 张量不起作用,在一些边缘情况下会有意外的行为。用户应该优先显式地将 ndarray 转换为张量。

注意

PyTorch 没有实现 __array_function____array_ufunc__。在底层,Tensor.__array__() 方法返回张量数据缓冲区的 NumPy ndarray 视图。详情请参阅 此问题torch_function 实现

还需注意,即使 torch.Tensor 不是 ndarray 的子类,我们也可以看到 __array_wrap__ 在这里发挥作用:

>>> import torch
>>> t = torch.arange(4)
>>> np.abs(t)
tensor([0, 1, 2, 3]) 

PyTorch 实现了 __array_wrap__ 来能够从 NumPy 函数中获取张量,并且我们可以直接修改它以控制从这些函数中返回哪种类型的对象。

例如:CuPy 数组

CuPy 是一个用于 GPU 加速计算的 NumPy/SciPy 兼容数组库。CuPy 通过实现 cupy.ndarray 实现了 NumPy 接口的子集,与 NumPy ndarrays 对应

>>> import cupy as cp
>>> x_gpu = cp.array([1, 2, 3, 4]) 

cupy.ndarray 对象实现了 __array_ufunc__ 接口。这使得可以将 NumPy ufuncs 应用于 CuPy 数组(这将推迟操作到与 ufunc 匹配的 CuPy CUDA/ROCm 实现):

>>> np.mean(np.exp(x_gpu))
array(21.19775622) 

请注意这些操作的返回类型仍与初始类型一致:

>>> arr = cp.random.randn(1, 2, 3, 4).astype(cp.float32)
>>> result = np.sum(arr)
>>> print(type(result))
<class 'cupy._core.core.ndarray'> 

请参阅 CuPy 文档中的此页面以获取详细信息

cupy.ndarray 也实现了 __array_function__ 接口,这意味着可以执行诸如

>>> a = np.random.randn(100, 100)
>>> a_gpu = cp.asarray(a)
>>> qr_gpu = np.linalg.qr(a_gpu) 

CuPy 在 cupy.ndarray 对象上实现了许多 NumPy 函数,但并非全部。详情请参阅 CuPy 文档

例如:Dask 数组

Dask 是 Python 中用于并行计算的灵活库。Dask Array 使用分块算法实现了 NumPy ndarray 接口的子集,将大数组切分成许多小数组。这允许使用多个核心对大于内存的数组进行计算。

Dask 支持 __array__()__array_ufunc__

>>> import dask.array as da
>>> x = da.random.normal(1, 0.1, size=(20, 20), chunks=(10, 10))
>>> np.mean(np.exp(x))
dask.array<mean_agg-aggregate, shape=(), dtype=float64, chunksize=(), chunktype=numpy.ndarray>
>>> np.mean(np.exp(x)).compute()
5.090097550553843 

注意

Dask 是惰性评估的,只有在通过调用 compute() 请求时,才会计算计算结果。

详细了解 Dask 数组文档Dask 数组与 NumPy 数组互操作性的范围

示例:DLPack

几个 Python 数据科学库实现了 __dlpack__ 协议。其中包括 PyTorchCuPy。可以在 DLPack 文档的这一页 找到实现此协议的库的完整列表。

将 PyTorch CPU 张量转换为 NumPy 数组:

>>> import torch
>>> x_torch = torch.arange(5)
>>> x_torch
tensor([0, 1, 2, 3, 4])
>>> x_np = np.from_dlpack(x_torch)
>>> x_np
array([0, 1, 2, 3, 4])
>>> # note that x_np is a view of x_torch
>>> x_torch[1] = 100
>>> x_torch
tensor([  0, 100,   2,   3,   4])
>>> x_np
array([  0, 100,   2,   3,   4]) 

导入的数组是只读的,因此无法进行写入或原地操作:

>>> x.flags.writeable
False
>>> x_np[1] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: assignment destination is read-only 

为了原地操作导入的数组,必须创建副本,但这将意味着复制内存。对于非常大的数组,请不要这样做:

>>> x_np_copy = x_np.copy()
>>> x_np_copy.sort()  # works 

注意

请注意,由于 NumPy 不支持 GPU 设备,无法将 GPU 张量转换为 NumPy 数组:

>>> x_torch = torch.arange(5, device='cuda')
>>> np.from_dlpack(x_torch)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Unsupported device in DLTensor. 

但是,如果两个库都支持数据缓冲区所在的设备,则可以使用 __dlpack__ 协议(例如 PyTorchCuPy):

>>> x_torch = torch.arange(5, device='cuda')
>>> x_cupy = cupy.from_dlpack(x_torch) 

类似地,可以将 NumPy 数组转换为 PyTorch 张量:

>>> x_np = np.arange(5)
>>> x_torch = torch.from_dlpack(x_np) 

只读数组无法导出:

>>> x_np = np.arange(5)
>>> x_np.flags.writeable = False
>>> torch.from_dlpack(x_np)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../site-packages/torch/utils/dlpack.py", line 63, in from_dlpack
  dlpack = ext_tensor.__dlpack__()
TypeError: NumPy currently only supports dlpack for writeable arrays 

进一步阅读

  • 数组接口协议

  • 编写自定义数组容器

  • 特殊属性和方法(关于 __array_ufunc____array_function__ 协议的详细信息)

  • 子类化 ndarray(关于 __array_wrap____array_finalize__ 方法的详细信息)

  • ndarray 子类型化的特定功能(有关 __array_finalize____array_wrap____array_priority__ 实现的更多详细信息)

  • NumPy 路线图:互操作性

  • PyTorch 与 NumPy 桥接的文档

1. 在 NumPy 中使用任意对象

NumPy API 的第一组互操作性功能允许在可能的情况下将外部对象视为 NumPy 数组。当 NumPy 函数遇到外部对象时,它们将按顺序尝试:

  1. 缓冲区协议,在 Python C-API 文档中 有描述。

  2. __array_interface__ 协议,在 这个页面 有描述。作为 Python 缓冲区协议的前身,它定义了从其他 C 扩展中访问 NumPy 数组内容的方法。

  3. __array__() 方法,请求任意对象将自身转换为数组。

对于缓冲区和 __array_interface__ 协议,对象描述其内存布局,NumPy 执行其他所有操作(如果可能,零拷贝)。 如果不可能,则对象本身负责从 __array__() 返回 ndarray

DLPack 是将外部对象以一种与语言和设备无关的方式转换为 NumPy 数组的另一种协议。 NumPy 不会使用 DLPack 将对象隐式转换为 ndarrays。 它提供了函数 numpy.from_dlpack ,该函数接受实现 __dlpack__ 方法的任何对象,并输出 NumPy ndarray(通常是输入对象数据缓冲区的视图)。 DLPack 的 Python 规范 页面详细说明了 __dlpack__ 协议。

数组接口协议

数组接口协议 定义了数组样对象重用彼此数据缓冲区的方式。 其实现依赖于以下属性或方法的存在:

  • __array_interface__:一个 Python 字典,包含类似数组对象的形状、元素类型,以及可选的数据缓冲地址和步幅;

  • __array__():返回类似数组对象的 NumPy ndarray 视图的方法;

可以直接检查 __array_interface__ 属性:

>>> import numpy as np
>>> x = np.array([1, 2, 5.0, 8])
>>> x.__array_interface__
{'data': (94708397920832, False), 'strides': None, 'descr': [('', '<f8')], 'typestr': '<f8', 'shape': (4,), 'version': 3} 

__array_interface__ 属性还可用于就地操作对象数据:

>>> class wrapper():
...     pass
...
>>> arr = np.array([1, 2, 3, 4])
>>> buf = arr.__array_interface__
>>> buf
{'data': (140497590272032, False), 'strides': None, 'descr': [('', '<i8')], 'typestr': '<i8', 'shape': (4,), 'version': 3}
>>> buf['shape'] = (2, 2)
>>> w = wrapper()
>>> w.__array_interface__ = buf
>>> new_arr = np.array(w, copy=False)
>>> new_arr
array([[1, 2],
 [3, 4]]) 

我们可以检查 arrnew_arr 是否共享相同的数据缓冲区:

>>> new_arr[0, 0] = 1000
>>> new_arr
array([[1000,    2],
 [   3,    4]])
>>> arr
array([1000, 2, 3, 4]) 

__array__() 方法

__array__() 方法确保任何类似 NumPy 的对象(数组、公开数组接口的任何对象、其 __array__() 方法返回数组的对象或任何嵌套序列),只要实现它,就可以用作 NumPy 数组。 如果可能,这将意味着使用 __array__() 来创建数组对象的 NumPy ndarray 视图。 否则,这将复制数据到一个新的 ndarray 对象中。 这不是最佳的,因为将数组强制转换为 ndarrays 可能会导致性能问题或创建副本和元数据丢失,因为原始对象及其可能具有的任何属性/行为都会丢失。

要查看自定义数组实现的示例,包括使用 __array__() 的用法,请参见 编写自定义数组容器。

DLPack 协议

DLPack 协议定义了分块的 n 维数组对象的内存布局。 它为数据交换提供了以下语法:

  1. numpy.from_dlpack 函数接受具有 __dlpack__ 方法的(数组)对象,并使用该方法构造一个包含来自 x 的数据的新数组。

  2. 数组对象上的__dlpack__(self, stream=None)__dlpack_device__方法将从from_dlpack中调用,以查询数组所在的设备(可能需要传递正确的流,例如在多个 GPU 的情况下),并访问数据。

与缓冲区协议不同,DLPack 允许交换包含在 CPU 之外设备上的数据的数组(例如 Vulkan 或 GPU)。由于 NumPy 仅支持 CPU,因此它只能转换其数据存在于 CPU 的对象。但其他库,如PyTorchCuPy,可以使用这个协议在 GPU 上交换数据。

数组接口协议

数组接口协议定义了类似数组的对象重复使用对方的数据缓冲区的方式。其实现依赖于以下属性或方法的存在:

  • __array_interface__:一个包含数组-like 对象的形状,元素类型,和可选的数据缓冲区地址和步幅的 Python 字典;

  • __array__():返回类似数组的对象的 NumPy ndarray 视图的方法;

可以直接检查__array_interface__属性:

>>> import numpy as np
>>> x = np.array([1, 2, 5.0, 8])
>>> x.__array_interface__
{'data': (94708397920832, False), 'strides': None, 'descr': [('', '<f8')], 'typestr': '<f8', 'shape': (4,), 'version': 3} 

__array_interface__属性还可以用于原地操作对象数据:

>>> class wrapper():
...     pass
...
>>> arr = np.array([1, 2, 3, 4])
>>> buf = arr.__array_interface__
>>> buf
{'data': (140497590272032, False), 'strides': None, 'descr': [('', '<i8')], 'typestr': '<i8', 'shape': (4,), 'version': 3}
>>> buf['shape'] = (2, 2)
>>> w = wrapper()
>>> w.__array_interface__ = buf
>>> new_arr = np.array(w, copy=False)
>>> new_arr
array([[1, 2],
 [3, 4]]) 

我们可以检查arrnew_arr是否共享相同的数据缓冲区:

>>> new_arr[0, 0] = 1000
>>> new_arr
array([[1000,    2],
 [   3,    4]])
>>> arr
array([1000, 2, 3, 4]) 

__array__()方法

__array__()方法确保任何类似 NumPy 的对象(数组,任何暴露数组接口的对象,其__array__()方法返回数组或任何嵌套序列的对象)都可以用作 NumPy 数组。如果可能的话,这意味着使用__array__()来创建类似数组对象的 NumPy ndarray 视图。否则,这将复制数据到一个新的 ndarray 对象中。这并不是最佳情况,因为将数组强制转换为 ndarrays 可能会导致性能问题或创建需要复制和丢失元数据的情况,因为原始对象及其可能具有的任何属性/行为都会丢失。

要查看包括使用__array__()的自定义数组实现的示例,请参见编写自定义数组容器。

DLPack 协议

DLPack协议定义了步进式 n 维数组对象的内存布局。它为数据交换提供了以下语法:

  1. numpy.from_dlpack函数,接受具有__dlpack__方法的(数组)对象,并使用该方法来构建包含x数据的新数组。

  2. 数组对象上的__dlpack__(self, stream=None)__dlpack_device__方法将从from_dlpack中调用,以查询数组所在的设备(可能需要传递正确的流,例如在多个 GPU 的情况下),并访问数据。

与缓冲协议不同,DLPack 允许交换包含设备上的数据(如 Vulkan 或 GPU)的数组。由于 NumPy 仅支持 CPU,因此只能转换数据存在于 CPU 上的对象。但其他库,如 PyTorchCuPy,可以使用该协议在 GPU 上交换数据。

2. 在不转换的情况下操作外部对象

NumPy API 定义的第二组方法允许我们将一个 NumPy 函数的执行延迟到另一个数组库。

考虑以下函数。

>>> import numpy as np
>>> def f(x):
...     return np.mean(np.exp(x)) 

请注意,np.exp 是一个 ufunc,这意味着它按元素方式在 ndarrays 上操作。另一方面,np.mean 沿数组的一个轴操作。

我们可以直接将 f 应用于 NumPy ndarray 对象:

>>> x = np.array([1, 2, 3, 4])
>>> f(x)
21.1977562209304 

我们希望这个函数在任何类似 NumPy 的数组对象上都能很好地工作。

NumPy 允许一个类通过以下接口指示它想通过自定义方式处理计算:

  • __array_ufunc__:允许第三方对象支持和覆盖 ufuncs。

  • __array_function__:用于处理通用函数的 NumPy 功能的总称,该功能不受通用函数协议 __array_ufunc__ 的覆盖。

只要外部对象实现了 __array_ufunc____array_function__ 协议,就可以在它们上操作而无需进行显式转换。

__array_ufunc__ 协议

通用函数(ufunc 简写)是一个“向量化”包装器,用于接收固定数量的特定输入并产生固定数量的特定输出的函数。如果不是所有的输入参数都是 ndarray,ufunc 的输出(及其方法)不一定是 ndarray。实际上,如果任何输入定义了 __array_ufunc__ 方法,控制权将完全传递给该函数,即通用函数被覆盖。在该(非 ndarray)对象上定义的 __array_ufunc__ 方法可以访问 NumPy ufunc。由于通用函数有明确定义的结构,外部的 __array_ufunc__ 方法可以依赖于类似 .at().reduce() 等的 ufunc 属性。

通过重写默认的 ndarray.__array_ufunc__ 方法,子类可以覆盖在其上执行 NumPy ufuncs 时的操作方式。这个方法会代替 ufunc 被执行,并应该返回操作的结果,或者在请求的操作未被实现时返回 NotImplemented

__array_function__ 协议

为了实现足够的 NumPy API 覆盖范围以支持下游项目,需要超出__array_ufunc__并实现一种协议,允许 NumPy 函数的参数控制并将执行转移到另一个函数(例如 GPU 或并行实现),以一种安全和一致的方式跨项目进行。

__array_function__ 的语义与 __array_ufunc__ 非常相似,只是操作由任意可调用对象指定,而不是 ufunc 实例和方法。具体详情请参见NEP 18 — NumPy 高级数组函数的调度机制

__array_ufunc__ 协议

通用函数(或简称 ufunc)是一个对函数进行“矢量化”封装的函数,它接受固定数量的特定输入,并产生固定数量的特定输出。 如果所有输入参数都不是 ndarray,则 ufunc 的输出(及其方法)未必是 ndarray。 实际上,如果任何输入定义了__array_ufunc__方法,则完全将控制权传递给该函数,即 ufunc 被覆盖。定义在那个(非 ndarray)对象上的__array_ufunc__方法可以访问 NumPy ufunc。 由于 ufuncs 有明确定义的结构,外部__array_ufunc__方法可以依赖于 ufunc 属性,例如.at().reduce()和其他属性。

子类可以通过覆盖默认的ndarray.__array_ufunc__方法来在执行 NumPy ufuncs 时改写其行为。这个方法将代替 ufunc 的执行,并应该返回操作的结果,或者如果请求的操作未实现,则返回NotImplemented

__array_function__ 协议

为了实现足够的 NumPy API 覆盖范围以支持下游项目,需要超出__array_ufunc__并实现一种协议,允许 NumPy 函数的参数控制并将执行转移到另一个函数(例如 GPU 或并行实现),以一种安全和一致的方式跨项目进行。

__array_function__ 的语义与 __array_ufunc__ 非常相似,只是操作由任意可调用对象指定,而不是 ufunc 实例和方法。具体详情请参见NEP 18 — NumPy 高级数组函数的调度机制

3. 返回外部对象

第三种特性集意在使用 NumPy 函数实现,然后将返回值转换回外部对象的实例。__array_finalize____array_wrap__方法在幕后起作用,以确保可以根据需要指定 NumPy 函数的返回类型。

__array_finalize__方法是 NumPy 提供的机制,允许子类处理创建新实例的各种方式。每当系统从数组的子类(子类型)的对象内部分配新数组时,就会调用此方法。它可用于在构造后更改属性,或者从“父级”更新元信息。

__array_wrap__方法“包装了操作”,在允许任何对象(如用户定义的函数)设置其返回值类型和更新属性和元数据方面发挥作用。这可以看作是__array__方法的相反。在实现__array_wrap__的每个对象的末尾,将对具有最高数组优先级的输入对象调用此方法,或者如果指定了输出对象,则在输出对象上调用此方法。__array_priority__属性用于确定在返回对象的 Python 类型存在多种可能性的情况下应返回什么类型的对象。例如,子类可能选择使用此方法将输出数组变换为子类实例并在返回数组给用户之前更新元数据。

有关这些方法的更多信息,请参阅子类化 ndarray 和 ndarray 子类型的特定特征。

互操作性示例

例子:Pandas Series对象

考虑以下内容:

>>> import pandas as pd
>>> ser = pd.Series([1, 2, 3, 4])
>>> type(ser)
pandas.core.series.Series 

现在,ser 不是一个 ndarray,但由于它实现了 array_ufunc 协议,我们可以将 ufuncs 应用于它,就好像它是一个 ndarray 一样:

>>> np.exp(ser)
 0     2.718282
 1     7.389056
 2    20.085537
 3    54.598150
 dtype: float64
>>> np.sin(ser)
 0    0.841471
 1    0.909297
 2    0.141120
 3   -0.756802
 dtype: float64 

我们甚至可以对其他 ndarray 执行操作:

>>> np.add(ser, np.array([5, 6, 7, 8]))
 0     6
 1     8
 2    10
 3    12
 dtype: int64
>>> f(ser)
21.1977562209304
>>> result = ser.__array__()
>>> type(result)
numpy.ndarray 

例子:PyTorch 张量

PyTorch是一个针对使用 GPU 和 CPU 进行深度学习的优化张量库。PyTorch 数组通常被称为张量。张量类似于 NumPy 的 ndarray,只不过张量可以在 GPU 或其他硬件加速器上运行。实际上,张量和 NumPy 数组通常可以共享相同的底层内存,消除了复制数据的需求。

>>> import torch
>>> data = [[1, 2],[3, 4]]
>>> x_np = np.array(data)
>>> x_tensor = torch.tensor(data) 

注意x_npx_tensor是不同类型的对象:

>>> x_np
array([[1, 2],
 [3, 4]])
>>> x_tensor
tensor([[1, 2],
 [3, 4]]) 

但是,我们可以将 PyTorch 张量视为 NumPy 数组,而无需显式转换:

>>> np.exp(x_tensor)
tensor([[ 2.7183,  7.3891],
 [20.0855, 54.5982]], dtype=torch.float64) 

此外,还要注意此函数的返回类型与初始数据类型兼容。

警告

虽然混合使用 ndarray 和张量可能很方便,但不建议这样做。它对非 CPU 张量不起作用,并且在一些特殊情况下会产生意外行为。用户应优先显式将 ndarray 转换为张量。

注意

PyTorch 不实现__array_function____array_ufunc__。在底层,Tensor.__array__()方法返回一个 NumPy ndarray,作为张量数据缓冲区的视图。有关详细信息,请参阅此问题torch_function 实现

还要注意,即使torch.Tensor不是 ndarray 的子类,我们也可以在这里看到__array_wrap__的实际运行:

>>> import torch
>>> t = torch.arange(4)
>>> np.abs(t)
tensor([0, 1, 2, 3]) 

PyTorch 实现了__array_wrap__以便从 NumPy 函数获取张量,并且我们可以直接修改它以控制从这些函数返回哪种类型的对象。

例:CuPy 数组

CuPy 是用于 GPU 加速计算的 NumPy/SciPy 兼容数组库。CuPy 通过实现cupy.ndarray实现了 NumPy 接口的子集,与 NumPy ndarrays 对应

>>> import cupy as cp
>>> x_gpu = cp.array([1, 2, 3, 4]) 

cupy.ndarray对象实现了__array_ufunc__接口。这使得可以对 CuPy 数组应用 NumPy ufunc(这将将操作推迟到对应的 CuPy CUDA/ROCm 实现的 ufunc):

>>> np.mean(np.exp(x_gpu))
array(21.19775622) 

请注意,这些操作的返回类型仍与初始类型保持一致:

>>> arr = cp.random.randn(1, 2, 3, 4).astype(cp.float32)
>>> result = np.sum(arr)
>>> print(type(result))
<class 'cupy._core.core.ndarray'> 

查看此页 CuPy 文档有关详细信息

cupy.ndarray还实现了__array_function__接口,这意味着可以进行诸如

>>> a = np.random.randn(100, 100)
>>> a_gpu = cp.asarray(a)
>>> qr_gpu = np.linalg.qr(a_gpu) 

CuPy 在cupy.ndarray对象上实现了许多 NumPy 函数,但不是全部。有关详细信息,请参阅CuPy 文档

例:Dask 数组

Dask 是 Python 中用于并行计算的灵活库。Dask 数组使用分块算法实现了 NumPy ndarray 接口的子集,将大数组切割成许多小数组。这允许使用多个核心对大于内存的数组进行计算。

Dask 支持__array__()__array_ufunc__

>>> import dask.array as da
>>> x = da.random.normal(1, 0.1, size=(20, 20), chunks=(10, 10))
>>> np.mean(np.exp(x))
dask.array<mean_agg-aggregate, shape=(), dtype=float64, chunksize=(), chunktype=numpy.ndarray>
>>> np.mean(np.exp(x)).compute()
5.090097550553843 

请注意

Dask 是延迟评估的,直到通过调用compute()要求计算结果才会计算。

有关详细信息,请参阅Dask 数组文档以及Dask 数组与 NumPy 数组的互操作性范围

例:DLPack

几个 Python 数据科学库实现了__dlpack__协议。其中包括PyTorchCuPy。可以在DLPack 文档的此页面找到实现此协议的库的完整列表。

将 PyTorch CPU 张量转换为 NumPy 数组:

>>> import torch
>>> x_torch = torch.arange(5)
>>> x_torch
tensor([0, 1, 2, 3, 4])
>>> x_np = np.from_dlpack(x_torch)
>>> x_np
array([0, 1, 2, 3, 4])
>>> # note that x_np is a view of x_torch
>>> x_torch[1] = 100
>>> x_torch
tensor([  0, 100,   2,   3,   4])
>>> x_np
array([  0, 100,   2,   3,   4]) 

导入的数组是只读的,因此写入或原地操作将失败:

>>> x.flags.writeable
False
>>> x_np[1] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: assignment destination is read-only 

必须创建副本才能对导入的数组进行原地操作,但这将意味着复制内存。对于非常大的数组不要这样做:

>>> x_np_copy = x_np.copy()
>>> x_np_copy.sort()  # works 

请注意

请注意,GPU 张量不能转换为 NumPy 数组,因为 NumPy 不支持 GPU 设备:

>>> x_torch = torch.arange(5, device='cuda')
>>> np.from_dlpack(x_torch)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Unsupported device in DLTensor. 

但是,如果这两个库都支持数据缓冲区所在的设备,则可以使用__dlpack__协议(例如PyTorchCuPy):

>>> x_torch = torch.arange(5, device='cuda')
>>> x_cupy = cupy.from_dlpack(x_torch) 

同样,NumPy 数组可以转换为 PyTorch 张量:

>>> x_np = np.arange(5)
>>> x_torch = torch.from_dlpack(x_np) 

只读数组无法导出:

>>> x_np = np.arange(5)
>>> x_np.flags.writeable = False
>>> torch.from_dlpack(x_np)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../site-packages/torch/utils/dlpack.py", line 63, in from_dlpack
  dlpack = ext_tensor.__dlpack__()
TypeError: NumPy currently only supports dlpack for writeable arrays 

示例:Pandas Series对象

考虑以下内容:

>>> import pandas as pd
>>> ser = pd.Series([1, 2, 3, 4])
>>> type(ser)
pandas.core.series.Series 

现在,ser是一个 ndarray,但因为它实现了__array_ufunc__协议,我们就可以像处理 ndarray 一样对其应用 ufunc:

>>> np.exp(ser)
 0     2.718282
 1     7.389056
 2    20.085537
 3    54.598150
 dtype: float64
>>> np.sin(ser)
 0    0.841471
 1    0.909297
 2    0.141120
 3   -0.756802
 dtype: float64 

我们甚至可以对其他 ndarrays 执行操作:

>>> np.add(ser, np.array([5, 6, 7, 8]))
 0     6
 1     8
 2    10
 3    12
 dtype: int64
>>> f(ser)
21.1977562209304
>>> result = ser.__array__()
>>> type(result)
numpy.ndarray 

示例:PyTorch 张量

PyTorch是一个用于在 GPU 和 CPU 上进行深度学习的优化张量库。PyTorch 数组通常被称为张量。张量类似于 NumPy 的 ndarrays,唯一的区别在于张量可以在 GPU 或其他硬件加速器上运行。实际上,张量和 NumPy 数组通常可以共享相同的底层存储器,消除了复制数据的需求。

>>> import torch
>>> data = [[1, 2],[3, 4]]
>>> x_np = np.array(data)
>>> x_tensor = torch.tensor(data) 

注意x_npx_tensor是不同类型的对象:

>>> x_np
array([[1, 2],
 [3, 4]])
>>> x_tensor
tensor([[1, 2],
 [3, 4]]) 

然而,我们可以将 PyTorch 张量视为 NumPy 数组,无需进行显式转换:

>>> np.exp(x_tensor)
tensor([[ 2.7183,  7.3891],
 [20.0855, 54.5982]], dtype=torch.float64) 

同样,请注意此函数的返回类型与初始数据类型兼容。

警告

虽然混合使用 ndarrays 和张量可能很方便,但不建议这样做。它不适用于非 CPU 张量,并且在极端情况下会出现意外行为。用户应该更倾向于显式将 ndarray 转换为张量。

注意

PyTorch 不实现__array_function____array_ufunc__。在内部,Tensor.__array__()方法返回一个 NumPy ndarray 作为张量数据缓冲区的视图。有关详细信息,请参见此问题torch_function 实现

还要注意,即使torch.Tensor不是 ndarray 的子类,我们也可以在这里看到__array_wrap__的功能:

>>> import torch
>>> t = torch.arange(4)
>>> np.abs(t)
tensor([0, 1, 2, 3]) 

PyTorch 实现了__array_wrap__以便能够从 NumPy 函数中取回张量,并且我们可以直接修改它以控制从这些函数返回哪种类型的对象。

示例:CuPy 数组

CuPy 是一个用于 GPU 加速计算的与 NumPy/SciPy 兼容的数组库。CuPy 通过实现cupy.ndarray与 NumPy ndarrays 对应的对象实现了 NumPy 接口的子集。

>>> import cupy as cp
>>> x_gpu = cp.array([1, 2, 3, 4]) 

cupy.ndarray对象实现了__array_ufunc__接口。这使得可以将 NumPy ufuncs 应用于 CuPy 数组(这将将操作延迟到 ufunc 的匹配 CuPy CUDA/ROCm 实现):

>>> np.mean(np.exp(x_gpu))
array(21.19775622) 

请注意这些操作的返回类型仍与初始类型一致:

>>> arr = cp.random.randn(1, 2, 3, 4).astype(cp.float32)
>>> result = np.sum(arr)
>>> print(type(result))
<class 'cupy._core.core.ndarray'> 

有关详细信息,请参见CuPy 文档中的此页面

cupy.ndarray还实现了__array_function__接口,这意味着可以进行诸如

>>> a = np.random.randn(100, 100)
>>> a_gpu = cp.asarray(a)
>>> qr_gpu = np.linalg.qr(a_gpu) 

CuPy 在cupy.ndarray对象上实现了许多 NumPy 函数,但并非都实现。有关详细信息,请参阅CuPy 文档

示例:Dask 数组

Dask 是 Python 中用于并行计算的灵活库。Dask Array 使用分块算法实现了 NumPy ndarray 接口的子集,将大数组切分为许多小数组。这使得可以使用多个核心对大于内存大小的数组进行计算。

Dask 支持__array__()__array_ufunc__

>>> import dask.array as da
>>> x = da.random.normal(1, 0.1, size=(20, 20), chunks=(10, 10))
>>> np.mean(np.exp(x))
dask.array<mean_agg-aggregate, shape=(), dtype=float64, chunksize=(), chunktype=numpy.ndarray>
>>> np.mean(np.exp(x)).compute()
5.090097550553843 

注意

Dask 是惰性计算的,计算的结果直到通过调用compute()来要求计算时才计算。

有关详细信息,请参阅 Dask 数组文档Dask 数组与 NumPy 数组互操作性的范围

示例:DLPack

几个 Python 数据科学库都实现了__dlpack__协议,其中包括PyTorchCuPy。可以在DLPack 文档的此页面找到实现此协议的库的完整列表。

将 PyTorch CPU 张量转换为 NumPy 数组:

>>> import torch
>>> x_torch = torch.arange(5)
>>> x_torch
tensor([0, 1, 2, 3, 4])
>>> x_np = np.from_dlpack(x_torch)
>>> x_np
array([0, 1, 2, 3, 4])
>>> # note that x_np is a view of x_torch
>>> x_torch[1] = 100
>>> x_torch
tensor([  0, 100,   2,   3,   4])
>>> x_np
array([  0, 100,   2,   3,   4]) 

导入的数组是只读的,因此写入或就地操作将失败:

>>> x.flags.writeable
False
>>> x_np[1] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: assignment destination is read-only 

为了就地操作导入的数组,必须创建副本,但这将意味着内存复制。对于非常大的数组不要这样做:

>>> x_np_copy = x_np.copy()
>>> x_np_copy.sort()  # works 

注意

注意 GPU 张量无法转换为 NumPy 数组,因为 NumPy 不支持 GPU 设备:

>>> x_torch = torch.arange(5, device='cuda')
>>> np.from_dlpack(x_torch)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Unsupported device in DLTensor. 

但是,如果两个库都支持数据缓冲区所在的设备,则可以使用__dlpack__协议(例如 PyTorchCuPy):

>>> x_torch = torch.arange(5, device='cuda')
>>> x_cupy = cupy.from_dlpack(x_torch) 

同样,可以将 NumPy 数组转换为 PyTorch 张量:

>>> x_np = np.arange(5)
>>> x_torch = torch.from_dlpack(x_np) 

只读数组无法导出:

>>> x_np = np.arange(5)
>>> x_np.flags.writeable = False
>>> torch.from_dlpack(x_np)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../site-packages/torch/utils/dlpack.py", line 63, in from_dlpack
  dlpack = ext_tensor.__dlpack__()
TypeError: NumPy currently only supports dlpack for writeable arrays 

进一步阅读

  • 数组接口协议

  • 编写自定义数组容器

  • 特殊属性和方法(关于__array_ufunc____array_function__协议的详细信息)

  • 子类化 ndarray(关于__array_wrap____array_finalize__方法的详细信息)

  • ndarray 子类型化的特定特性(关于__array_finalize____array_wrap____array_priority__实现的更多详细信息)

  • NumPy 路线图:互操作性

  • PyTorch 与 NumPy 之间的桥接文档

04-29 09:31