-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
WIP: Remove fragile use of __array_interface__
in ctypeslib.as_array
#10970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
'data': (ct.addressof(self), False), | ||
} | ||
|
||
simple_type.__array_interface__ = property(__array_interface__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Monkey-patching ctypes was undocumented, and has confused me in the past!
# Prep that numerical ctypes types: | ||
for types, code in simple_types: | ||
for tp in types: | ||
prep_simple(tp, "%c%d" % (code, ct.sizeof(tp))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already encoded in the implementation of dtype.str.__get__
While we're talking about PEP3118 and ctypes - could I ask someone who's worked with PEP3118 (@ahaldane?) to give a review to python/cpython#5561 and python/cpython#5576? It would be great if numpy would work with ctype structs as well, and those PRs have been sitting idle for a while |
numpy/ctypeslib.py
Outdated
|
||
def as_array(obj, shape=None): | ||
def as_array(obj, shape=()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be safer to have shape=None
and in the body of the code if shape is None: shape = ()
While in the current version shape
is read-only, this might future-proof the function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Safer from what perspective?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops, shape is a zero-length-tuple so immutable. Never mind, sorry for the noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've made me realize that defaulting to None and erroring is probably closer to the old behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to reject shape=None
7038d28
to
2bb3d77
Compare
I suspect this fixes gh-6511 too |
I'm hoping that this also fixes the daily wheels builds that have been broken since #10968 went in.
|
It looks like you've truncated that error message by at least one character (a I don't predict this patch will fix that. Strange that it works fine on travis, but not the wheel builds. |
Also works on Mac and Windows, only Yeah, I kind of thought this PR wouldn't help, but wanted to get the ball rolling ... |
@charris: First step would be to not replace the error message coming from the internal PEP3118 parser with that less useful one. |
No, but maybe we should. Python 2.7.7 came out about 4 years ago and these days it is pretty easy to upgrade. Be worth raising the issue on the list. There were several ctypes fixes in the recently released Python 2.7.15, so if we do pick a version, it should only be new enough that our tests pass and otherwise recommend that folks stay current. |
numpy/ctypeslib.py
Outdated
return { | ||
_dtype(ctype).str: ctype | ||
for ctype in simple_types | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would read better if the }
was at the end of the previous line. This looks too much like C.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe do the dictionary creation separately from the return
statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have different stylistic opinions here. How about I just put it on one line, since it fits nicely anyway?
numpy/ctypeslib.py
Outdated
'data': (ct.addressof(contents), False), | ||
'shape': shape} | ||
def _ctype_ndarray(el_type, shape): | ||
""" Create an ndarray of the given element type and shape """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is el_type
? A complete docstring would help here.
numpy/ctypeslib.py
Outdated
|
||
def _get_typecodes(): | ||
""" Get a dictionary mapping __array_interface__ formats to ctypes types """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe """Return ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
################################################################ | ||
# public functions | ||
if ctypes is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we still need this. Much of the code here is ancient and predates the inclusion of ctypes into the standard library in Python 2.5.
The numpy array shares the memory with the ctypes object. | ||
|
||
The size parameter must be given if converting from a ctypes POINTER. | ||
The size parameter is ignored if converting from a ctypes array | ||
The shape parameter must be given if converting from a ctypes POINTER. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A standard docstring would help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to argue this is out of scope for this patch
prep_pointer(obj, shape) | ||
else: | ||
prep_array(tp) | ||
if isinstance(obj, ctypes._Pointer): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is using private class safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of using attribute testing here, which will give a false-positive for np.recarray
with the wrong dtype. I suppose I could switch it back to how the detection was done before, if you'd prefer that.
@@ -21,11 +21,12 @@ | |||
except ImportError: | |||
_HAS_CTYPE = False | |||
|
|||
|
|||
@pytest.mark.skipif(not _HAS_CTYPE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above, ctypes should be available in the Python versions we support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed below
@@ -113,3 +111,59 @@ def test_cache(self): | |||
a1 = ndpointer(dtype=np.float64) | |||
a2 = ndpointer(dtype=np.float64) | |||
assert_(a1 == a2) | |||
|
|||
|
|||
@pytest.mark.skipif(not _HAS_CTYPE, |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
numpy/tests/test_ctypeslib.py
Outdated
class TestAsArray(object): | ||
def test_array(self): | ||
from ctypes import c_int | ||
at = c_int * 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at
could be more descriptive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now pair_t
reason="ctypes not available on this python installation") | ||
class TestAsArray(object): | ||
def test_array(self): | ||
from ctypes import c_int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blank line after imports is standard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
assert_array_equal(a, np.array([[1, 2], [3, 4], [5, 6]])) | ||
|
||
def test_pointer(self): | ||
from ctypes import c_int, cast, POINTER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
assert_raises(TypeError, as_array, p) | ||
|
||
def test_struct_array_pointer(self): | ||
from ctypes import c_int16, Structure, pointer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
The presence of |
I disagree - we've had bug reports about this in the past. It seems that python can be built without ctypes on some platforms? |
See this comment for a summary of how to end up without ctypes |
Hmm, it actually seems reasonable that ctypes may not be available on all platforms/environments. It certainly doesn't hurt to deal with that case. |
2bb3d77
to
f6a6009
Compare
Some nits addressed. I think this does not change the patch-version-worry introduced by #10882 |
Everything behaves a lot better if we let the array constructor handle it, which will use the ctypes PEP3118 support. Bugs this fixes: * Stale state being attached to pointer objects (fixes numpygh-2671, closes numpygh-6214) * Weird failure modes on structured arrays (fixes-10978) * A regression in numpygh-10882 (fixes numpygh-10968)
f6a6009
to
8a8be50
Compare
Thanks Eric. |
__array_interface__
in ctypeslib.as_array
At this point, we can rely on PEP3118 support within ctypes and numpy
Fixes gh-10968
Fixes gh-2671
Closes gh-6214 (commit is cherry-picked, thanks @tynn!)