【问题标题】:a.transpose().ravel()[0]=x cannot change value of original array in numpy?a.transpose().ravel()[0]=x 不能改变numpy中原始数组的值?
【发布时间】:2019-01-13 04:44:30
【问题描述】:

环境:Python 3.6.0 |Anaconda 自定义(64 位),numpy 版本:1.11.3
示例:

In[1]: import numpy as np
In[2]: a = np.array([[1,2,3], [4,5,6]])
In[3]: a
Out[4]: 
array([[1, 2, 3],
       [4, 5, 6]])
In[5]: a.transpose()[0] = -1
In[6]: a
Out[6]: 
array([[-1,  2,  3],
       [-1,  5,  6]])
In[7]: a.ravel()[0] = -2 
In[8]: a
Out[8]: 
array([[-2,  2,  3],
       [-1,  5,  6]])
In[9]: a.transpose().ravel()[0] = -3
In[10]: a
Out[10]: 
array([[-2,  2,  3],
       [-1,  5,  6]])

我知道transpose()ravel() 返回一个数组视图,所以我们可以改变它原来数组的值。但是,当我们使用transpose().ravel()时,我们不能改变它吗?为什么?

【问题讨论】:

    标签: python numpy deep-copy


    【解决方案1】:

    ravel 正在返回一个副本,而不是一个视图

    来自numpy.ravel docs

    返回一个包含输入元素的一维数组。仅在需要时才制作副本。

    因此,基本上,在整理转置时,实际上需要一个副本。您正在更改副本中的值,因此不会反映在原始数组中。

    测试返回的数组是视图还是副本

    对于这样一个简单的情况,您可以通过比较b.basea 的身份来测试数组b 是否是a 的视图:

    a = np.array([[1,2,3], [4,5,6]])
    b = a.T
    c = b.ravel()
    
    print('b is a view of a\n%s\n' % (b.base is a))
    print('c is a view of a\n%s\n' % (c.base is a))
    

    输出:

    b is a view of a
    True
    
    c is a view of a
    False
    

    为什么a.T.ravel() 会返回一个副本?

    Shocker:实际上有一种方法可以让a.T.ravel() 返回视图而不是副本。你可以通过显式设置order='F'(即Fortran顺序)来做到这一点:

    a = np.array([[1,2,3], [4,5,6]])
    c = a.T.ravel()
    d = a.T.ravel(order='F')
    
    print('d is a view of a\n%s\n' % (d.base is a))
    

    输出:

    d is a view of a
    True
    

    但是,更改 order kwarg 的值将更改 raveled 数组中值的顺序(花哨的):

    print('c\n%s\n' % c)
    print('d\n%s\n' % d)
    

    输出:

    c
    [1 4 2 5 3 6]
    
    d
    [1 2 3 4 5 6]
    

    为了理解为什么order 的更改会导致视图被返回,我们可以查看ravel 函数本身的代码。 np.ndarray.ravel 的实现是buried in the C layer。阅读源代码,很明显,为了从ravel 返回视图,必须满足两个条件:

    • 输入数组必须是连续的。

    • 连续输入数组的顺序必须与传递给ravelorder kwarg 的顺序相匹配。

    kwarg 的默认值为order='C'。因此,默认情况下,ravel 仅在您在 C 连续数组上运行时才会返回视图。大多数情况下,当您初始化一个新的 Numpy 数组 a 时,它将以 C 连续开始。但是,转置a.T 将是 F 连续的。通过检查数组的.flags property,您可以在代码中看到这一点:

    a = np.array([[1,2,3], [4,5,6]])
    
    print('the flags of a\n%s\n' % a.flags)
    print('the flags of a.T\n%s\n' % a.T.flags)
    

    输出:

    the flags of a
      C_CONTIGUOUS : True
      F_CONTIGUOUS : False
      OWNDATA : True
      WRITEABLE : True
      ALIGNED : True
      WRITEBACKIFCOPY : False
      UPDATEIFCOPY : False
    
    the flags of a.T
      C_CONTIGUOUS : False
      F_CONTIGUOUS : True
      OWNDATA : False
      WRITEABLE : True
      ALIGNED : True
      WRITEBACKIFCOPY : False
      UPDATEIFCOPY : False
    

    C 和 F 连续是什么意思?

    C-contiguous 和 F-contiguous 术语对您来说很可能是胡言乱语。解释它们需要另一个问题,很高兴有人在 SO 上已经问过了。这是a link to an old answer,它非常直观地概述了 C 和 F 顺序的实际含义。

    警告

    在您的实际代码中,我不会太担心ravel 是返回视图还是副本。实际上,您并不总是通过确保使用视图来提高性能。一般避免过早优化。

    【讨论】:

    • 为什么要拆散转置,实际上需要一个副本?
    • 好的,我已经添加了所有关于为什么ravel 返回副本而不是视图的详细信息。可悲的是,它有点复杂(大约是我原来答案的 3 倍)。
    【解决方案2】:
    In [382]: a = np.array([[1,2,3], [4,5,6]])
    In [383]: a
    Out[383]: 
    array([[1, 2, 3],
           [4, 5, 6]])
    In [384]: a.ravel()
    Out[384]: array([1, 2, 3, 4, 5, 6])
    

    ravel 提供数组的一维视图 - 并按照它们在数据缓冲区中出现的顺序显示值。

    In [385]: a.T
    Out[385]: 
    array([[1, 4],
           [2, 5],
           [3, 6]])
    In [386]: a.T.ravel()
    Out[386]: array([1, 4, 2, 5, 3, 6])
    

    ravel 的转置以不同的顺序显示元素 - 除非我们将顺序指定为“F”(或“K”)。

    In [387]: a.T.ravel(order='F')
    Out[387]: array([1, 2, 3, 4, 5, 6])
    

    ravel(和其他操作)如果数组可以使用原始数据,则生成view,仅更改shapestrides。如果不能,则必须制作副本。

    由于转置的元素顺序发生了这种变化,使用 [0] 以外的其他内容进行索引会选择不同的值:

    In [397]: a.ravel()[3]
    Out[397]: 4               # -1 in your Out[8]
    In [398]: a.T.ravel()[3]
    Out[398]: 5
    

    当您要求更改转置的第四个元素时,您会发现存在一定的歧义。它可能因您遍历元素的方式而异。

    【讨论】:

      猜你喜欢
      • 2019-03-11
      • 2020-07-29
      • 1970-01-01
      • 2021-10-17
      • 1970-01-01
      • 2020-04-07
      • 1970-01-01
      • 1970-01-01
      • 2015-08-17
      相关资源
      最近更新 更多