numpy.array切片行为

时间:2017-06-28 19:12:09

标签: python arrays numpy slice

为什么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]似乎引用原始列表的值,修改它也会修改一个!

>>> 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

1 个答案:

答案 0 :(得分: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和指针)。