【问题标题】:numpy.array slicing behaviournumpy.array 切片行为
【发布时间】:2017-06-28 19:12:09
【问题描述】:

为什么在切片方面numpy.array 的行为与 Python 的列表和默认数组不同?请考虑以下示例:

1)使用列表:语句b = a[1:3]创建一个新的列表对象,修改b不会修改a

>>> a = [1,2,3,4]
>>> b = a[1:3]
>>> print(b)
[2, 3]
>>> b[0] = -17
>>> print(b)
[-17, 3]
>>> print(a)
[1, 2, 3, 4]

2)使用array.array:语句b = a[1:3]再次创建一个新的数组对象,修改b不会修改a

>>> import array
>>> a = array.array('i', [1,2,3,4])
>>> b = a[1:3]
>>> print(b)
array('i', [2, 3])
>>> b[0] = -17
>>> print(b)
array('i', [-17, 3])
>>> print(a)
array('i', [1, 2, 3, 4])

3) 使用numpy.array:语句b = a[1:3] 似乎引用了原始列表的值,修改它也确实修改了a!

>>> import numpy
>>> a = numpy.array([1,2,3,4])
>>> b = a[1:3]
>>> print(b)
[2 3]
>>> b[0] = -17
>>> print(b)
[-17   3]
>>> print(a)
[  1 -17   3   4]

问题是:为什么numpy 中会出现这种行为?

【问题讨论】:

    标签: python arrays numpy slice


    【解决方案1】:

    因为 NumPy 是一种高性能的数据收集。为了让 Python 创建一个新列表,它必须构造一个新列表,递增指向列表中每个元素的所有指针,将项目添加到列表中,然后返回切片。 NumPy(可能)只是增加起始数组的偏移量并更改数组的结尾。

    NumPy 切片

    将 NumPy 数组想象成这样(是的,这过于简单化了):

    struct array
    {
        size_t type_size;
        size_t length
        void* start;
    };
    

    如果你不懂C,那么基本上意味着一个数组可以被认为是一个内存地址,指定数组的开始,它存储它所存储的每种类型的大小,然后是长度缓冲区。对于整数数组,我们的 type_size 可能为 4,在本例中,长度为 5(对于 20 字节的缓冲区)。

    在切片时,NumPy 可以简单地增加起始点并减小大小,而不是复制整个数据。

    array slice(array* array, size_t start, size_t end)
    {
        array arr = *array;
        arr.start = (char*)arr.start + start;
        arr.length = end - start;
        return arr;
    }
    

    这比为新列表分配内存然后将这些指针分配(并递增,Python 是引用计数)到列表中要便宜得多。

    Python 切片

    这是一个简化的 Python 示例:

    PyObject* slice(PyObject* list, size_t start, size_t end)
    {
        size_t length = end - start;
        PyObject* out = PyList_New(length);
        for (size_t i = start; size_t i < end; ++i) {
            PyObject*item = PyList_GetItem(list, i);
            PyList_Append(&out, i);
        }
    
        return out;
    }
    

    注意这涉及多少?还有更多内容。

    理性

    思考性能:NumPy 要想拥有原来的切片行为,就必须在内存中占用一个新地址(因为数据在内存中是连续的)。这意味着复制数据,可能是通过memcpy()。这很昂贵:假设我有一个 20,000 np.int32 (~80 KB) 的数组,我需要将所有这些数据复制到一个新数组中。在上面的切片示例中,我只复制了大约 24 字节的内存(假设 8 字节的 size_t 和指针)。

    【讨论】:

      猜你喜欢
      • 2017-10-04
      • 1970-01-01
      • 2017-08-24
      • 1970-01-01
      • 1970-01-01
      • 2019-12-25
      • 2019-12-13
      • 2018-01-30
      • 1970-01-01
      相关资源
      最近更新 更多