Skip to content

Commit

Permalink
Merge pull request numpy#12571 from charris/revert-11721
Browse files Browse the repository at this point in the history
Revert "Merge pull request numpy#11721 from eric-wieser/fix-9647"
  • Loading branch information
charris authored Dec 17, 2018
2 parents 7ce7382 + 46445c2 commit 059196b
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 105 deletions.
5 changes: 0 additions & 5 deletions doc/release/1.16.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,6 @@ copying the data directly into the appropriate slice of the resulting array.
This results in significant speedups for these large arrays, particularly for
arrays being blocked along more than 2 dimensions.

``arr.ctypes.data_as(...)`` holds a reference to arr
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously the caller was responsible for keeping the array alive for the
lifetime of the pointer.

Speedup ``np.take`` for read-only arrays
----------------------------------------
The implementation of ``np.take`` no longer makes an unnecessary copy of the
Expand Down
8 changes: 8 additions & 0 deletions numpy/core/_add_newdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,14 @@
.. automethod:: numpy.core._internal._ctypes.strides_as
Be careful using the ctypes attribute - especially on temporary
arrays or arrays constructed on the fly. For example, calling
``(a+b).ctypes.data_as(ctypes.c_void_p)`` returns a pointer to memory
that is invalid because the array created as (a+b) is deallocated
before the next Python statement. You can avoid this problem using
either ``c=a+b`` or ``ct=(a+b).ctypes``. In the latter case, ct will
hold a reference to the array until ct is deleted or re-assigned.
If the ctypes module is not available, then the ctypes attribute
of array objects still returns something useful, but ctypes objects
are not returned and errors may be raised instead. In particular,
Expand Down
73 changes: 15 additions & 58 deletions numpy/core/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,45 +238,19 @@ def _getintp_ctype():

class _missing_ctypes(object):
def cast(self, num, obj):
return obj.value

class c_void_p(object):
def __init__(self, ptr):
self.value = ptr



def _get_void_ptr(arr):
"""
Get a `ctypes.c_void_p` to arr.data, that keeps a reference to the array
"""
import numpy as np
# don't let subclasses interfere
arr = arr.view(ndarray)
# collapse the array to point to at most 1 element, so it become contiguous
arr = arr[np.s_[:1,] * arr.ndim + np.s_[...,]]
# then convert to ctypes now that we've reduced it to a simple, empty, array
arr.flags.writeable = True
arr = (ctypes.c_char * 0).from_buffer(arr)
# finally cast to void*
return ctypes.cast(ctypes.pointer(arr), ctypes.c_void_p)
return num

def c_void_p(self, num):
return num

class _ctypes(object):
def __init__(self, array, ptr=None):
self._arr = array

if ctypes:
self._ctypes = ctypes
# get a void pointer to the buffer, which keeps the array alive
self._data = _get_void_ptr(array)
assert self._data.value == ptr
else:
# fake a pointer-like object that holds onto the reference
self._ctypes = _missing_ctypes()
self._data = self._ctypes.c_void_p(ptr)
self._data._objects = array

self._arr = array
self._data = ptr
if self._arr.ndim == 0:
self._zerod = True
else:
Expand All @@ -289,8 +263,6 @@ def data_as(self, obj):
``self.data_as(ctypes.c_void_p)``. Perhaps you want to use the data as a
pointer to a ctypes array of floating-point data:
``self.data_as(ctypes.POINTER(ctypes.c_double))``.
The returned pointer will keep a reference to the array.
"""
return self._ctypes.cast(self._data, obj)

Expand All @@ -312,8 +284,7 @@ def strides_as(self, obj):
return None
return (obj*self._arr.ndim)(*self._arr.strides)

@property
def data(self):
def get_data(self):
"""
A pointer to the memory area of the array as a Python integer.
This memory area may contain data that is not aligned, or not in correct
Expand All @@ -322,16 +293,10 @@ def data(self):
attribute to arbitrary C-code to avoid trouble that can include Python
crashing. User Beware! The value of this attribute is exactly the same
as ``self._array_interface_['data'][0]``.
Note that unlike `data_as`, a reference will not be kept to the array:
code like ``ctypes.c_void_p((a + b).ctypes.data)`` will result in a
pointer to a deallocated array, and should be spelt
``(a + b).ctypes.data_as(ctypes.c_void_p)``
"""
return self._data.value
return self._data

@property
def shape(self):
def get_shape(self):
"""
(c_intp*self.ndim): A ctypes array of length self.ndim where
the basetype is the C-integer corresponding to ``dtype('p')`` on this
Expand All @@ -342,8 +307,7 @@ def shape(self):
"""
return self.shape_as(_getintp_ctype())

@property
def strides(self):
def get_strides(self):
"""
(c_intp*self.ndim): A ctypes array of length self.ndim where
the basetype is the same as for the shape attribute. This ctypes array
Expand All @@ -353,20 +317,13 @@ def strides(self):
"""
return self.strides_as(_getintp_ctype())

@property
def _as_parameter_(self):
"""
Overrides the ctypes semi-magic method
Enables `c_func(some_array.ctypes)`
"""
return self._data
def get_as_parameter(self):
return self._ctypes.c_void_p(self._data)

# kept for compatibility
get_data = data.fget
get_shape = shape.fget
get_strides = strides.fget
get_as_parameter = _as_parameter_.fget
data = property(get_data)
shape = property(get_shape)
strides = property(get_strides)
_as_parameter_ = property(get_as_parameter, None, doc="_as parameter_")


def _newnames(datatype, order):
Expand Down
40 changes: 0 additions & 40 deletions numpy/core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -7626,46 +7626,6 @@ def test_ctypes_is_not_available(self):
finally:
_internal.ctypes = ctypes

def _make_readonly(x):
x.flags.writeable = False
return x

@pytest.mark.parametrize('arr', [
np.array([1, 2, 3]),
np.array([['one', 'two'], ['three', 'four']]),
np.array((1, 2), dtype='i4,i4'),
np.array([None], dtype=object),
np.array([]),
np.empty((0, 0)),
_make_readonly(np.array([1, 2, 3])),
], ids=[
'1d',
'2d',
'structured',
'object',
'empty',
'empty-2d',
'readonly'
])
def test_ctypes_data_as_holds_reference(self, arr):
# gh-9647
# create a copy to ensure that pytest does not mess with the refcounts
arr = arr.copy()

arr_ref = weakref.ref(arr)

ctypes_ptr = arr.ctypes.data_as(ctypes.c_void_p)

# `ctypes_ptr` should hold onto `arr`
del arr
gc.collect()
assert_(arr_ref() is not None, "ctypes pointer did not hold onto a reference")

# but when the `ctypes_ptr` object dies, so should `arr`
del ctypes_ptr
gc.collect()
assert_(arr_ref() is None, "unknowable whether ctypes pointer holds a reference")


class TestWritebackIfCopy(object):
# all these tests use the WRITEBACKIFCOPY mechanism
Expand Down
4 changes: 2 additions & 2 deletions numpy/ma/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2401,9 +2401,9 @@ def test_inplace_addition_scalar(self):
assert_equal(xm, y + 1)

(x, _, xm) = self.floatdata
id1 = x.data.ctypes.data
id1 = x.data.ctypes._data
x += 1.
assert_(id1 == x.data.ctypes.data)
assert_(id1 == x.data.ctypes._data)
assert_equal(x, y + 1.)

def test_inplace_addition_array(self):
Expand Down

0 comments on commit 059196b

Please sign in to comment.