【问题标题】:gotchas where Numpy differs from straight python?Numpy 与直接 python 的区别在哪里?
【发布时间】:2010-11-22 06:49:06
【问题描述】:

伙计们,

是否存在 Numpy 与 python 不同的陷阱集合, 有困惑又费时间的点?

“那一刻的恐怖我将 永远不会忘记!”
“不过,你会的,”女王说,“如果你不 做个备忘录。”

例如,NaN 在任何地方总是很麻烦。 如果你不用运行就可以解释这个,给自己一个点--

from numpy import array, NaN, isnan

pynan = float("nan")
print pynan is pynan, pynan is NaN, NaN is NaN
a = (0, pynan)
print a, a[1] is pynan, any([aa is pynan for aa in a])

a = array(( 0, NaN ))
print a, a[1] is NaN, isnan( a[1] )

(我不是在敲 numpy,那里有很多出色的工作,只是认为常见问题解答或问题 Wiki 会很有用。)

编辑:我希望收集六个陷阱(对于学习 Numpy 的人来说是个惊喜)。
那么,如果有共同的陷阱,或者更好的,共同的解释, 我们可以讨论将它们添加到社区 Wiki(在哪里?) 到目前为止,我们似乎还不够。

【问题讨论】:

  • 应该是社区维基
  • 没有人提到原始类型。这是否意味着python float 等价于 np.float 等?

标签: python numpy


【解决方案1】:

Numpy 数组的真值与 python 序列类型的真值不同,其中任何非空序列都为真。

>>> import numpy as np
>>> l = [0,1,2,3]
>>> a = np.arange(4)
>>> if l: print "Im true"
... 
Im true
>>> if a: print "Im true"
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use
a.any() or a.all()
>>>

数字类型在非零时为真,并且作为数字的集合,numpy 数组继承了这个定义。但是对于一组数字,真理可以合理地表示“所有元素都非零”或“至少一个元素非零”。 Numpy 拒绝猜测是指哪个定义并引发上述异常。使用 .any().all() 方法可以指定 true 的含义。

>>> if a.any(): print "Im true"
... 
Im true
>>> if a.all(): print "Im true"
... 
>>>

【讨论】:

    【解决方案2】:
    In [1]: bool([])
    Out[1]: False
    
    In [2]: bool(array([]))
    Out[2]: False
    
    In [3]: bool([0])
    Out[3]: True
    
    In [4]: bool(array([0]))
    Out[4]: False
    

    所以不要通过检查数组的真值来测试数组是否为空。使用size(array())

    也不要使用len(array())

    In [1]: size(array([]))
    Out[1]: 0
    
    In [2]: len(array([]))
    Out[2]: 0
    
    In [3]: size(array([0]))
    Out[3]: 1
    
    In [4]: len(array([0]))
    Out[4]: 1
    
    In [5]: size(array(0))
    Out[5]: 1
    
    In [6]: len(array(0))
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-6-5b2872696128> in <module>()
    ----> 1 len(array(0))
    
    TypeError: len() of unsized object
    

    【讨论】:

    • 你建议使用size(array(.)),那么使用len(array(.))呢?
    • 不,lensize 不同。 size(array(0)) → 1 因为 0-rank 数组中有一个值,而 len(array(0)) 引发异常 len() of unsized object
    • 看起来又是一个陷阱。 array(0) 应该是有效构造的原因是什么?
    【解决方案3】:

    *= 分配与numpy.array 结合的惊喜:

    >>> from numpy import array
    
    >>> a = array([1, 2, 3])
    >>> a *= 1.1  
    >>> print(a) 
    [1 2 3]  # not quite what we expect or would like to see
    
    >>> print(a.dtype)
    int64  # and this is why
    
    >>> a = 1.1 * a  # here, a new array is created
    >>> print(a, a.dtype)
    [ 1.1  2.2  3.3] float64  # with the expected outcome
    

    令人惊讶,烦人,但可以理解。 *= 运算符不会更改 array 数据的类型,因此 int arrayfloat 的乘法将在此乘法的传统含义中失败。另一方面,Python 版本 a = 1; a *= 1.1 按预期工作。

    【讨论】:

      【解决方案4】:

      没有那么大的问题:使用布尔切片,我有时希望我能做到

        x[ 3 <= y < 7 ]
      

      就像 python 的双重比较。相反,我必须写

        x[ np.logical_and(3<=y, y<7) ]
      

      (除非你知道更好的东西?)

      另外,np.logical_and 和 np.logical_or 每个只接受两个参数,我希望它们接受一个变量号或一个列表,这样我就可以输入两个以上的逻辑子句。

      (numpy 1.3,也许这在以后的版本中都改变了。)

      【讨论】:

      • x[(3
      【解决方案5】:

      None 的 0-d 数组看起来像 None 但不一样:

      In [1]: print None
      None
      
      In [2]: import numpy
      
      In [3]: print numpy.array(None)
      None
      
      In [4]: numpy.array(None) is None
      Out[4]: False
      
      In [5]: numpy.array(None) == None
      Out[5]: False
      
      In [6]: print repr(numpy.array(None))
      array(None, dtype=object)
      

      【讨论】:

        【解决方案6】:

        到目前为止似乎没有人提到这一点:

        >>> all(False for i in range(3))
        False
        >>> from numpy import all
        >>> all(False for i in range(3))
        True
        >>> any(False for i in range(3))
        False
        >>> from numpy import any
        >>> any(False for i in range(3))
        True
        

        numpy 的 anyall 不能很好地与生成器配合使用,并且不会引发任何错误警告您它们没有。

        【讨论】:

          【解决方案7】:

          (相关,但 NumPy 与 SciPy 的陷阱,而不是 NumPy 与 Python)


          超出数组实际大小的切片工作方式不同:

          >>> import numpy, scipy.sparse
          
          >>> m = numpy.random.rand(2, 5) # create a 2x5 dense matrix
          >>> print m[:3, :] # works like list slicing in Python: clips to real size
          [[ 0.12245393  0.20642799  0.98128601  0.06102106  0.74091038]
          [ 0.0527411   0.9131837   0.6475907   0.27900378  0.22396443]]
          
          >>> s = scipy.sparse.lil_matrix(m) # same for csr_matrix and other sparse formats
          >>> print s[:3, :] # doesn't clip!
          IndexError: row index out of bounds
          

          因此,在对 scipy.sparse 数组进行切片时,您必须手动确保切片边界在范围内。这与 NumPy 和普通 Python 的工作方式不同。

          【讨论】:

            【解决方案8】:

            我觉得这个很有趣:

            >>> import numpy as n
            >>> a = n.array([[1,2],[3,4]])
            >>> a[1], a[0] = a[0], a[1]
            >>> a
            array([[1, 2],
                   [1, 2]])
            

            另一方面,对于 Python 列表,这可以按预期工作:

            >>> b = [[1,2],[3,4]]
            >>> b[1], b[0] = b[0], b[1]
            >>> b
            [[3, 4], [1, 2]]
            

            有趣的旁注:numpy 本身在shuffle 函数中有一个错误,因为它使用了该符号:-)(请参阅here)。

            原因是在第一种情况下,我们正在处理数组的 views,因此值会被原地覆盖。

            【讨论】:

            • 你能解释一下 numpy 数组是如何结束的吗?
            【解决方案9】:

            因为__eq__ 不返回布尔值,所以在任何类型的容器中使用 numpy 数组都会阻止相等性测试,而无需针对容器进行解决。

            例子:

            >>> import numpy
            >>> a = numpy.array(range(3))
            >>> b = numpy.array(range(3))
            >>> a == b
            array([ True,  True,  True], dtype=bool)
            >>> x = (a, 'banana')
            >>> y = (b, 'banana')
            >>> x == y
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
            ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
            

            这是一个可怕的问题。例如,您不能为使用 TestCase.assertEqual() 的容器编写单元测试,而必须编写自定义比较函数。假设我们编写了一个变通函数special_eq_for_numpy_and_tuples。现在我们可以在单元测试中做到这一点:

            x = (array1, 'deserialized')
            y = (array2, 'deserialized')
            self.failUnless( special_eq_for_numpy_and_tuples(x, y) )
            

            现在我们必须对可能用于存储 numpy 数组的每种容器类型执行此操作。此外,__eq__ 可能会返回一个 bool 而不是 bool 数组:

            >>> a = numpy.array(range(3))
            >>> b = numpy.array(range(5))
            >>> a == b
            False
            

            现在我们每个特定于容器的相等比较函数也必须处理这种特殊情况。

            也许我们可以用一个子类来修补这个疣?

            >>> class SaneEqualityArray (numpy.ndarray):
            ...   def __eq__(self, other):
            ...     return isinstance(other, SaneEqualityArray) and self.shape == other.shape and (numpy.ndarray.__eq__(self, other)).all()
            ... 
            >>> a = SaneEqualityArray( (2, 3) )
            >>> a.fill(7)
            >>> b = SaneEqualityArray( (2, 3) )
            >>> b.fill(7)
            >>> a == b
            True
            >>> x = (a, 'banana')
            >>> y = (b, 'banana')
            >>> x == y
            True
            >>> c = SaneEqualityArray( (7, 7) )
            >>> c.fill(7)
            >>> a == c
            False
            

            这似乎是对的。该类还应该显式导出元素比较,因为这通常很有用。

            【讨论】:

              【解决方案10】:

              我发现将元素列表相乘只会创建元素视图这一事实让我很着迷。

              >>> a=[0]*5
              >>>a
              [0,0,0,0,0]
              >>>a[2] = 1
              >>>a
              [0,0,1,0,0]
              >>>b = [np.ones(3)]*5
              >>>b
              [array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.])]
              >>>b[2][1] = 2
              >>>b
              [array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.])]
              

              因此,如果您创建一个这样的元素列表并打算对它们执行不同的操作,那么您将陷入困境......

              一个简单的解决方案是迭代地创建每个数组(使用“for 循环”或列表推导)或使用更高维数组(例如,这些 1D 数组中的每一个都是 2D 数组中的一行,这通常是更快)。

              【讨论】:

              • 这看起来像一个纯 python 陷阱,与 numpy 完全无关。
              • jolvi ...看起来更近一点...尝试使用 range(3) 而不是 np.ones(3) ...这就是一个 numpy 陷阱的微妙之处。这是一种有意的行为,因为它通常会使代码更快并且可以解决。
              • [range(3)] * 5 计算结果为 [range(0, 3), range(0, 3), range(0, 3), range(0, 3), range(0, 3)]。如果我这样做a = [list(range(3))] * 5; a[0][0] = 33,我会看到与numpy 情况完全相同:5 次引用一个list 实例,第一个元素设置为33,就像你所做的那样一个numpy.array 实例。
              • operation [whatever] * 5 是,AFAICS,与 whatever 完全无关,并且与 numpy 完全无关。它产生 5 个对同一个对象的引用。如果您更改对象,您将在所有 5 引用中看到更改。
              【解决方案11】:

              切片创建视图,而不是副本。

              >>> l = [1, 2, 3, 4]
              >>> s = l[2:3]
              >>> s[0] = 5
              >>> l
              [1, 2, 3, 4]
              
              >>> a = array([1, 2, 3, 4])
              >>> s = a[2:3]
              >>> s[0] = 5
              >>> a
              array([1, 2, 5, 4])
              

              【讨论】:

              【解决方案12】:

              来自 Neil Martinsen-Burrell,numpy-discussion 9 月 7 日 --

              Numpy 中可用的 ndarray 类型是 不是概念上的扩展 Python 的可迭代对象。如果你想 帮助其他 Numpy 用户 问题,您可以编辑文档 在在线文档编辑器中 numpy-docs

              【讨论】:

                【解决方案13】:

                对我来说最大的问题是几乎每个标准运算符都被重载以分布在数组中。

                定义一个列表和一个数组

                >>> l = range(10)
                >>> l
                [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                >>> import numpy
                >>> a = numpy.array(l)
                >>> a
                array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
                

                乘法复制 python 列表,但分布在 numpy 数组上

                >>> l * 2
                [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
                >>> a * 2
                array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
                

                python列表上没有定义加法和除法

                >>> l + 2
                Traceback (most recent call last):
                  File "<stdin>", line 1, in <module>
                TypeError: can only concatenate list (not "int") to list
                >>> a + 2
                array([ 2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
                >>> l / 2.0
                Traceback (most recent call last):
                  File "<stdin>", line 1, in <module>
                TypeError: unsupported operand type(s) for /: 'list' and 'float'
                >>> a / 2.0
                array([ 0. ,  0.5,  1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5])
                

                Numpy 重载有时将列表视为数组

                >>> a + a
                array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
                >>> a + l
                array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
                

                【讨论】:

                • 是的,这也吸引了我。一个带有列的简单表:op、python、numpy 可以解决这个问题。
                • 其实a + a为列表定义的。它将它们连接起来。
                【解决方案14】:

                NaN 不是像None 这样的单例,所以你不能真正使用 is 检查它。让它有点棘手的是NaN == NaNFalse,正如 IEEE-754 所要求的那样。这就是为什么您需要使用numpy.isnan() 函数来检查浮点数是否不是数字的原因。或者,如果您使用的是 Python 2.6+,则使用标准库 math.isnan()

                【讨论】:

                • 嗯,它在 NaN 的定义中。 def isnan(x): return (x != x)
                【解决方案15】:
                print pynan is pynan, pynan is NaN, NaN is NaN
                

                这会测试身份,即是否是同一个对象。因此,结果显然应该是 True、False、True,因为当您执行 float(whatever) 时,您正在创建一个新的 float 对象。

                a = (0, pynan)
                print a, a[1] is pynan, any([aa is pynan for aa in a])
                

                我不知道你对此感到惊讶的是什么。

                a = array(( 0, NaN ))
                print a, a[1] is NaN, isnan( a[1] )
                

                这是我必须运行的。 :-) 当您将 NaN 粘贴到数组中时,它会转换为 numpy.float64 对象,这就是 a[1] is NaN 失败的原因。

                这一切对我来说似乎并不令人惊讶。但是我对 NumPy 的了解并不多。 :-)

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-08-29
                  • 2013-09-29
                  • 1970-01-01
                  • 2010-11-17
                  • 2014-08-18
                  • 2019-03-14
                  相关资源
                  最近更新 更多