Comparing NumPy object references

arrays, identity, numpy, python

Solution

2d slicing

When I first wrote this I constructed and indexed a 1d array. But the OP is working with a 2d array, so `x[0]` is a 'row', a slice of the original.

In [81]: arr = np.array([[1,2,3], [4,5,6]])
In [82]: arr.__array_interface__['data']
Out[82]: (181595128, False)

In [83]: x0 = arr[0,:]
In [84]: x0.__array_interface__['data']
Out[84]: (181595128, False)        # same databuffer pointer
In [85]: id(x0)
Out[85]: 2886887088
In [86]: x1 = arr[0,:]             # another slice, different id
In [87]: x1.__array_interface__['data']
Out[87]: (181595128, False)
In [88]: id(x1)
Out[88]: 2886888888

What I wrote earlier about slices still applies. Indexing an individual elements, as with `arr[0,0]` works the same as with a 1d array.

This 2d arr has the same databuffer as the 1d `arr.ravel()`; the shape and strides are different. And the distinction between `view`, `copy` and `item` still applies.

A common way of implementing 2d arrays in C is to have an array of pointers to other arrays. `numpy` takes a different, `strided` approach, with just one flat array of data, and uses`shape` and `strides` parameters to implement the transversal. So a subarray requires its own `shape` and `strides` as well as a pointer to the shared databuffer.

1d array indexing

I'll try to illustrate what is going on when you index an array:

In [51]: arr = np.arange(4)

The array is an object with various attributes such as shape, and a data buffer. The buffer stores the data as bytes (in a C array), not as Python numeric objects. You can see information on the array with:

In [52]: np.info(arr)
class:  ndarray
shape:  (4,)
strides:  (4,)
itemsize:  4
aligned:  True
contiguous:  True
fortran:  True
data pointer: 0xa84f8d8
byteorder:  little
byteswap:  False
type: int32

or

In [53]: arr.__array_interface__
Out[53]: 
{'data': (176486616, False),
 'descr': [('', '<i4')],
 'shape': (4,),
 'strides': None,
 'typestr': '<i4',
 'version': 3}

One has the data pointer in hex, the other decimal. We usually don't reference it directly.

If I index an element, I get a new object:

In [54]: x1 = arr[1]
In [55]: type(x1)
Out[55]: numpy.int32
In [56]: x1.__array_interface__
Out[56]: 
{'__ref': array(1),
 'data': (181158400, False),
....}
In [57]: id(x1)
Out[57]: 2946170352

It has some properties of an array, but not all. For example you can't assign to it. Notice also that its 'data` value is totally different.

Make another selection from the same place - different id and different data:

In [58]: x2 = arr[1]
In [59]: id(x2)
Out[59]: 2946170336
In [60]: x2.__array_interface__['data']
Out[60]: (181143288, False)

Also if I change the array at this point, it does not affect the earlier selections:

In [61]: arr[1] = 10
In [62]: arr
Out[62]: array([ 0, 10,  2,  3])
In [63]: x1
Out[63]: 1

`x1` and `x2` don't have the same `id`, and thus won't match with `is`, and they don't use the `arr` data buffer either. There's no record that either variable was derived from `arr`.

With `slicing` it is possible get a `view` of the original array,

In [64]: y = arr[1:2]
In [65]: y.__array_interface__
Out[65]: 
{'data': (176486620, False),
 'descr': [('', '<i4')],
 'shape': (1,),
 ....}
In [66]: y
Out[66]: array([10])
In [67]: y[0]=4
In [68]: arr
Out[68]: array([0, 4, 2, 3])
In [69]: x1
Out[69]: 1

It's data pointer is 4 bytes larger than `arr` - that is, it points to the same buffer, just a different spot. And changing `y` does change `arr` (but not the independent `x1`).

I could even make a 0d view of this item

In [71]: z = y.reshape(())
In [72]: z
Out[72]: array(4)
In [73]: z[...]=0
In [74]: arr
Out[74]: array([0, 0, 2, 3])

In Python code we normally don't work with objects like this. When we use the `c-api` or `cython` is it possible to access the data buffer directly. `nditer` is an iteration mechanism that works with 0d objects like this (either in Python or the c-api). In `cython` `typed memoryviews` are particularly useful for low level access.

http://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html

https://docs.scipy.org/doc/numpy/reference/arrays.nditer.html

https://docs.scipy.org/doc/numpy/reference/c-api.iterator.html#c.NpyIter

elementwise ==

In response to comment, Comparing NumPy object references

np.array([1]) == np.array([2]) will return array([False], dtype=bool)

`==` is defined for arrays as an elementwise operation. It compares the values of the respective elements and returns a matching boolean array.

If such a comparison needs to be used in a scalar context (such as an `if`) it needs to be reduced to a single value, as with `np.all` or `np.any`.

The `is` test compares object id's (not just for numpy objects). It has limited value in practical coding. I used it most often in expressions like `is None`, where `None` is an object with a unique id, and which does not play nicely with equality tests.

Problem

I want to understand the NumPy behavior. When I try to get the reference of an inner array of a NumPy array, and then compare it to the object itself, I get as returned value `False`. Here is the example: ``` In [198]: x = np.array([[1,2,3], [4,5,6]]) In [201]: x0 = x[0] In [202]: x0 is x[0] Out[202]: False ``` While on the other hand, with Python native objects, the returned is `True`. ``` In [205]: c = [[1,2,3],[1]] In [206]: c0 = c[0] In [207]: c0 is c[0] Out[207]: True ``` My question, is that the intended behavior of NumPy? If so, what should I do if I want to create a reference of inner objects of NumPy arrays.

Original source