Repository navigation
BUG: Fix pickle and memoryview for datetime64, timedelta64 scalars - #11694
Conversation
| info = _buffer_get_info(self); | ||
| if (info == NULL) { | ||
| PyErr_SetString(PyExc_BufferError, | ||
| if (!PyErr_Occurred()) { |
There was a problem hiding this comment.
I don't think this if can ever be hit (at least, after gh-11695)
There was a problem hiding this comment.
Try setting a breakpoint there and calling memoryview(np.datetime64('2016-01-01')) or more to the point of the issue pickle.dumps(np.datetime64('2016-01-01')). The call chain is gettype_getbuffer -> _buffer_get_info -> _buffer_info_new -> _buffer_format_string -> switch statement -> missing case marked with XXX -> raising error with "cannot include dtype '%c' in a buffer".
There was a problem hiding this comment.
To be clear - I think _buffer_get_info is well behaved, and will always set an exception if it returns NULL.
|
In case it helps, this was the old behavior |
|
The memoryview info for |
|
I cleared the WIP in the title, this seems to work now. |
| dt1 = np.datetime64('2016-01-01') | ||
| dt2 = np.datetime64('2017-01-01') | ||
| expected = {'strides': (1,), 'itemsize': 1, 'ndim': 1, | ||
| 'shape': (8,), 'format': 'B'} |
There was a problem hiding this comment.
One interesting idea would be to set the format to T{8B:value:T{}:__numpy_dtype_m8[N]:} so that we can actually round-trip this
| assert_equal(res, expected) | ||
| assert_equal(v.tolist(), lst) | ||
|
|
||
| lst = [110, 1, 0, 0, 0, 0, 0, 0] |
There was a problem hiding this comment.
I think this might be making assumptions about endianness
| assert_(base <= sys.getrefcount(s)) | ||
|
|
||
| @pytest.mark.skipif(sys.version_info < (3, 0), reason="Python3 only") | ||
| def test_datetime_memoryview(self): |
There was a problem hiding this comment.
I think this test might be better alongside wherever the other memoryview tests are
| case NPY_UBYTE: if (_append_char(str, 'B')) return -1; break; | ||
| case NPY_UBYTE: | ||
| case NPY_DATETIME: | ||
| case NPY_TIMEDELTA: if (_append_char(str, 'B')) return -1; break; |
There was a problem hiding this comment.
Would it be legal to set the format to 8B instead, and leave the shape as ()?
There was a problem hiding this comment.
Why not 'Q' then, since we call these datetime64 and timedelta64, the 64 bit is explicit. This would mean that d=np.ones((3), 'M8[D]'); np.array(memoryview(d)) would return a uint64 array, rather that a 3 by 8 'uint8' one.
|
Can you add a test for |
c38cd2b to
63e44e3
Compare
Added tests for both scalars and arrays. After changing the format string to 'Q' I could remove the special casing in I tried to hit the "Sanity check" errors in tests. Should I go to C to create non-compliant buffers? |
|
Would this be easier to review if I move the last commit 63e44e3 to a separate PR? Then we could get this into 1.15.1 without changing the buffer format from
|
|
Does the 'Q' format need a prefix, '=' perhaps, to indicate standard size (8) and avoid compiler dependence? I'm thinking of https://docs.python.org/2/library/struct.html. |
I agree - let's leave the buffer format as it was in 1.14.x for the 1.15.x series - we can change it in 1.16 What do you mean by "a doesn't succeed"? |
| "Item size computed from the PEP 3118 buffer format " | ||
| "string does not match the actual item size."); | ||
| "Item size %d for PEP 3118 buffer format " | ||
| "string %s does not match the dtype %c item size %d.", |
There was a problem hiding this comment.
Better to use the INTP format macro here (or perhaps %z) for itemsize, rather than casting.
Edit: %zd
I mean that executing the line |
|
I backed out the last commit to fix only the regression on pyhon 3 between 1.14 and 1.15 |
|
What is the exception that's thrown? |
|
added test to |
69f279d to
27cd5a0
Compare
|
@eric-wieser Look good? |
|
I'm not really a fan of this approach - what we've ended up doing is:
This does at least meet the requirement of not giving broken behavior to the user. I'm not sure if there's an easier way to achieve this. |
There was a problem hiding this comment.
Can't you just set the format directly here and return early, rather than modifying _buffer_format_string to do something incorrect?
There was a problem hiding this comment.
This comment should be explaining that the format is being exposed as B - scalars are always 1 is false - sizeof(B) == 1 is what's relevant here.
There was a problem hiding this comment.
removing code, comment expanded above.
There was a problem hiding this comment.
Whoops, this is needed. Comment fixed.
There was a problem hiding this comment.
I assume that in the tests this does occur? If so, perhaps say when it occurs in the comment.
There was a problem hiding this comment.
I think shape and strides are uninitialized here - they're malloc'd below for some cases.
There was a problem hiding this comment.
fixed to be compliant with _buffer_info_t allocation strategy
ce53937 to
eb1b209
Compare
|
Special-cased |
There was a problem hiding this comment.
malloc can fail - this needs to call PyErr_NoMemory() if so
There was a problem hiding this comment.
I'd prefer this appear after the comment, alongside all the other assigments
eric-wieser
left a comment
There was a problem hiding this comment.
Looks good now, thanks for the persistence. Will merge and squash if you don't want to squash yourself.
There was a problem hiding this comment.
Something for another PR: We ought to be checking the return value of _append_char, but we don't
|
sqaushed to two commits. |
| assert_raises((ValueError, BufferError), memoryview, a) | ||
| assert_raises((ValueError, BufferError), memoryview, np.array((3), 'M8[D]')) | ||
|
|
||
|
|
There was a problem hiding this comment.
super-nit: too many blank lines. Not worth fixing though - something to pick up if / when we apply a PEP8-checker
|
Thanks @mattip! |
Fixes #11656, which is a regression after #10564.
Adds tests for pickling datetime64 and timedelta64 scalars. Pickling arrays of these
dtypes works but pickling scalars fails.The problem seems to be in the new code for
gentype_getbuffer, which creates a PEP 3118 compliant buffer. The old code was:so now I need to find what is happening in that code that is not happening in the new code.
So far I only found where we override an original exception with a new one.
Nowmemoryview(np.datetime64('2016-01-01'))raises an exception with the same message as `memoryview(np.array(100, 'M8[D]').Edit: python2 - python3 confusion