Python Numpy安全编程

Python Numpy安全编程,python,numpy,Python,Numpy,使用numpy进行安全、无bug的数值编程有什么来源或指南吗 我这么问是因为我痛苦地了解到numpy做了很多事情,似乎真的会让bug发生,比如 添加不同大小的矩阵(“广播”),无需抱怨: In: np.array([1]) + np.identity(2) Out: array([[ 2., 1.], [ 1., 2.]]) 根据输入返回不同的数据类型: In: scalar1 = 1 In: scalar2 = 1. In: np.array(scalar1).d

使用numpy进行安全、无bug的数值编程有什么来源或指南吗

我这么问是因为我痛苦地了解到numpy做了很多事情,似乎真的会让bug发生,比如

添加不同大小的矩阵(“广播”),无需抱怨:

In: np.array([1]) + np.identity(2)
Out: array([[ 2.,  1.],
            [ 1.,  2.]])
根据输入返回不同的数据类型:

In: scalar1 = 1
In: scalar2 = 1.
In: np.array(scalar1).dtype
Out: dtype('int32')
In: np.array(scalar2).dtype
Out: dtype('float64')
或者只是不执行所需的操作(同样,取决于数据类型),而不发出任何警告:

In: np.squeeze(np.array([[1, 1]])).ndim
Out: 1
In: np.squeeze(np.matrix([[1, 1]])).ndim
Out: 2
这些bug都很难发现,因为它们不会引发任何异常或警告,并且通常会返回有效数据类型/形状的结果。因此,我的问题是:在使用numpy进行数学编程时,是否有提高安全性和防止错误的一般指导原则?


[请注意,我不认为这个答案会吸引“固执己见的答案和讨论”,因为它不是关于个人建议,而是询问是否有关于这个主题的任何现有指导方针或来源,我找不到任何指导方针或来源。]

如果您想以“更安全”的方式使用numpy,你可能需要建立自己的安全网。一种方法是定义包装器,以强制执行您希望代码遵守的规则。你可以想出你自己的包装和测试,当你走和/或绊倒的行为,你认为有问题。

一些玩具示例:

始终使用浮点数组:

禁用广播:

使用
np.matrix
时发出警告:


在某些方面,这个问题的答案与安全编程的一般指南没有什么不同:

  • 对于每个功能,尽早检查并清理代码
  • 维护相关的单元测试
是的,这听起来像是额外的开销,但事实是,您可能已经在手工进行此类检查和测试,因此最好将其写在纸上,并使流程正式化/自动化。例如,虽然您可能从未预期过矩阵输出,但任何检查您的输出是否为预期阵列的单元测试都会失败

您可能还想看看专门针对科学代码的专用测试工具,例如软件包


numpy特有的一件事是处理浮动错误;默认设置只是将警告语句“打印”到stdout,这很容易被忽略(并且不适合适当的异常处理工作流)。您可以将此功能转换为抛出可以通过
numpy.seterr
方法捕获的适当警告/异常,例如
numpy.seterr(all='raise')

我经常问提问者,
形状是什么?
dtype
?即使是
类型
。跟踪这些属性是良好的
numpy
编程的重要组成部分。即使是在MATLAB中,我发现获得正确的
大小
也是调试的80%

类型
挤压
示例围绕
类型
ndarray
类与
np.matrix
子类展开:

In [160]: np.squeeze(np.array([[1, 1]]))
Out[160]: array([1, 1])
In [161]: np.squeeze(np.matrix([[1, 1]]))
Out[161]: matrix([[1, 1]])
根据定义,矩阵对象总是二维的。这就是它如何重新定义
ndarray
操作的核心

许多
numpy
函数将其工作委托给
方法。“挤压”的代码是:

try:
    squeeze = a.squeeze
except AttributeError:
    return _wrapit(a, 'squeeze')
try:
    # First try to use the new axis= parameter
    return squeeze(axis=axis)
except TypeError:
    # For backwards compatibility
    return squeeze()
所以[161]中的
实际上是:

In [163]: np.matrix([[1, 1]]).squeeze()
Out[163]: matrix([[1, 1]])
np.matrix.squence
有自己的文档

作为一般规则,我们不鼓励使用
np.matrix
。这是多年前为任性的MATLAB程序员创造的一个工具。在那些日子里,MATLAB只有2d矩阵(即使现在MATLAB的“标量”也是2d)

数据类型
np.array
是一个强大的函数。通常它的行为是直观的,但有时它会做出太多的假设

通常,它从输入中获取线索,无论是整数、浮点、字符串还是列表:

In [170]: np.array(1).dtype
Out[170]: dtype('int64')
In [171]: np.array(1.0).dtype
Out[171]: dtype('float64')
但它提供了许多参数。如果需要更多的控制,请使用这些选项:

array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)

In [173]: np.array(1, float).dtype
Out[173]: dtype('float64')
In [174]: np.array('1', float).dtype
Out[174]: dtype('float64')
In [177]: np.array('1', dtype=float,ndmin=2)
Out[177]: array([[1.]])
查看它的文档,以及列出许多其他数组创建函数的页面。看看他们的一些代码

例如
np。至少\u 2d
进行了大量
形状检查:

def atleast_2d(*arys):
    res = []
    for ary in arys:
        ary = asanyarray(ary)
        if ary.ndim == 0:
            result = ary.reshape(1, 1)
        elif ary.ndim == 1:
            result = ary[newaxis,:]
        else:
            result = ary
        res.append(result)
    if len(res) == 1:
        return res[0]
    else:
        return res
像这样的函数是防御性编程的好例子

我们得到了很多关于
dtype=object
的一维数组的问题

In [272]: np.array([[1,2,3],[2,3]])
Out[272]: array([list([1, 2, 3]), list([2, 3])], dtype=object)
np.array
尝试使用统一的
dtype
创建多维数组。但是,如果元素大小不同或者不能转换为相同的
dtype
,它将返回到
object
dtype。这是我们需要注意
shape
dtype
的情况之一

广播 广播一直是
numpy
的一部分,没有办法关闭它。Octave和MATLAB后来添加了它,并且启用了警告开关

第一步是理解广播原则,即

  • 它可以展开起始尺寸以匹配
  • 它强制统一维度匹配
因此,一个基本的例子是:

In [180]: np.arange(3)[:,None] + np.arange(4)
Out[180]: 
array([[0, 1, 2, 3],
       [1, 2, 3, 4],
       [2, 3, 4, 5]])
第一项是(3,)扩展到(3,1)。第二个是(4),通过广播扩展到(1,4)。(3,1)和(1,4)一起向(3,4)广播

许多numpy函数的参数使跟踪尺寸变得更容易。例如,
sum
(和其他)有一个
keepdims
参数:

In [181]: arr = _
In [182]: arr.sum(axis=0)
Out[182]: array([ 3,  6,  9, 12])         # (4,) shape
In [183]: arr.sum(axis=0,keepdims=True)
Out[183]: array([[ 3,  6,  9, 12]])       # (1,4) shape
In [184]: arr/_                           # (3,4) / (1,4) => (3,4)
Out[184]: 
array([[0.        , 0.16666667, 0.22222222, 0.25      ],
       [0.33333333, 0.33333333, 0.33333333, 0.33333333],
       [0.66666667, 0.5       , 0.44444444, 0.41666667]])
在这种情况下,
keepdims
不是必需的,因为(3,4)/(4,)可以工作。但是当轴=1时,形状变为(3,)而不能与(3,4)一起广播。但是(3,1)可以:

要管理形状,我喜欢:

  • 调试时显示
    形状
  • 以交互方式测试代码段
  • 使用诊断形状进行测试,例如
    np.arange(24)。重塑(2,3,4)
  • In [272]: np.array([[1,2,3],[2,3]]) Out[272]: array([list([1, 2, 3]), list([2, 3])], dtype=object)
In [180]: np.arange(3)[:,None] + np.arange(4)
Out[180]: 
array([[0, 1, 2, 3],
       [1, 2, 3, 4],
       [2, 3, 4, 5]])
In [181]: arr = _
In [182]: arr.sum(axis=0)
Out[182]: array([ 3,  6,  9, 12])         # (4,) shape
In [183]: arr.sum(axis=0,keepdims=True)
Out[183]: array([[ 3,  6,  9, 12]])       # (1,4) shape
In [184]: arr/_                           # (3,4) / (1,4) => (3,4)
Out[184]: 
array([[0.        , 0.16666667, 0.22222222, 0.25      ],
       [0.33333333, 0.33333333, 0.33333333, 0.33333333],
       [0.66666667, 0.5       , 0.44444444, 0.41666667]])
In [185]: arr/arr.sum(axis=1,keepdims=True)
Out[185]: 
array([[0.        , 0.16666667, 0.33333333, 0.5       ],
       [0.1       , 0.2       , 0.3       , 0.4       ],
       [0.14285714, 0.21428571, 0.28571429, 0.35714286]])