-
-
Notifications
You must be signed in to change notification settings - Fork 11k
ENH: properly account for trailing padding in PEP3118 #7798
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Trailing Padding now supported in PEP3118 buffer inferface | ||
---------------------------------------------------------- | ||
Previously, structured types with trailing padding such as | ||
`np.dtype({'formats': ['i1'], 'names': ['a'], 'itemsize': 4})` could not | ||
roundtrip through the PEP3118 interface using a memoryview, as in | ||
`a == np.array(memoryview(a))`. Now, such trailing padding is preserved. | ||
|
||
More technically, the PEP3118 interface now supports PEP3118 format strings as | ||
follows: Within "T{}", in aligned @ mode, trailing padding is automatically | ||
assumed in the same way as C structs and numpy aligned dtypes. Outside of T{} | ||
trailing padding is not automatically added or assumed in inputs, following | ||
python's struct module, but is explicitly added by padding with "x" or unnamed | ||
zero-sized trailing elements. 0-sized unnamed elements, like "0i", can now be | ||
added anywhere in the format string, and in @ mode this will add padding bytes | ||
up to that type's alignment offset, and otherwise is ignored, as described in | ||
the python struct docs. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7023,27 +7023,72 @@ def test_native_padding_2(self): | |
self._check('^x3T{xi}', {'f0': (({'f0': ('i', 1)}, (3,)), 1)}) | ||
|
||
def test_trailing_padding(self): | ||
# Trailing padding should be included, *and*, the item size | ||
# should match the alignment if in aligned mode | ||
align = np.dtype('i').alignment | ||
size = np.dtype('i').itemsize | ||
|
||
def aligned(n): | ||
return align*(1 + (n-1)//align) | ||
|
||
base = dict(formats=['i'], names=['f0']) | ||
|
||
self._check('ix', dict(itemsize=aligned(size + 1), **base)) | ||
self._check('ixx', dict(itemsize=aligned(size + 2), **base)) | ||
self._check('ixxx', dict(itemsize=aligned(size + 3), **base)) | ||
self._check('ixxxx', dict(itemsize=aligned(size + 4), **base)) | ||
self._check('i7x', dict(itemsize=aligned(size + 7), **base)) | ||
bbase = dict(formats=['b'], names=['f0']) | ||
|
||
self._check('ix', dict(itemsize=size + 1, **base)) | ||
self._check('ixx', dict(itemsize=size + 2, **base)) | ||
self._check('ixxx', dict(itemsize=size + 3, **base)) | ||
self._check('ixxxx', dict(itemsize=size + 4, **base)) | ||
self._check('i7x', dict(itemsize=size + 7, **base)) | ||
self._check('ix0i', dict(itemsize=2*size, **base)) | ||
self._check('b0i', dict(itemsize=size, **bbase)) | ||
|
||
# Our intepretaton of the PEP3118/struct spec is that trailing | ||
# padding for alignment is assumed only inside of T{}. | ||
self._check('T{ix}', dict(itemsize=aligned(size + 1), **base)) | ||
self._check('T{ixx}', dict(itemsize=aligned(size + 2), **base)) | ||
self._check('T{ixxx}', dict(itemsize=aligned(size + 3), **base)) | ||
self._check('T{ixxxx}', dict(itemsize=aligned(size + 4), **base)) | ||
self._check('T{i7x}', dict(itemsize=aligned(size + 7), **base)) | ||
self._check('T{ix0i}', dict(itemsize=2*size, **base)) | ||
self._check('T{b0i}', dict(itemsize=size, **bbase)) | ||
|
||
# check that alignment mode affects assumed trailing padding in T{} | ||
self._check('T{=ix}', dict(itemsize=size + 1, **base)) | ||
|
||
self._check('^ix', dict(itemsize=size + 1, **base)) | ||
self._check('^ixx', dict(itemsize=size + 2, **base)) | ||
self._check('^ixxx', dict(itemsize=size + 3, **base)) | ||
self._check('^ixxxx', dict(itemsize=size + 4, **base)) | ||
self._check('^i7x', dict(itemsize=size + 7, **base)) | ||
self._check('^ixx0i', dict(itemsize=size + 2, **base)) | ||
self._check('^b0i', np.dtype('b')) | ||
|
||
# check we can convert to memoryview and back, aligned and unaligned | ||
arr = np.zeros(3, dtype=np.dtype('u1,i4,u1', align=True)) | ||
assert_equal(arr.dtype, np.array(memoryview(arr)).dtype) | ||
|
||
arr = np.zeros(3, dtype=np.dtype('u1,i4,u1', align=False)) | ||
assert_equal(arr.dtype, np.array(memoryview(arr)).dtype) | ||
|
||
a = np.empty(0, np.dtype({'formats': ['u1'], 'offsets': [0], | ||
'names': ['x'], 'itemsize': 4})) | ||
assert_equal(a, np.array(memoryview(a))) | ||
|
||
# check that 0-sized elements act as padding in @ alignment and not = | ||
# outside of T{} (see python struct docs, example at very end) | ||
self._check('B:f0:B:f1:', [('f0', 'u1'), ('f1', 'u1')]) | ||
self._check('B:f0:0iB:f1:0i', {'names': ['f0','f1'], | ||
'formats': ['u1','u1'], | ||
'offsets': [0,4], | ||
'itemsize': 8}) | ||
self._check('=B:f0:0iB:f1:0i', [('f0', 'u1'), ('f1', 'u1')]) | ||
|
||
# PEP3118 cannot support overlapping/out-of-order fields | ||
# (update these tests if it is improved to allow this) | ||
a = np.empty(3, dtype={'names': ['a', 'b'], | ||
'formats': ['i4', 'i2'], | ||
'offsets': [0, 2]}) | ||
assert_raises(ValueError, memoryview, a) | ||
a = np.empty(3, dtype='i4,i4')[['f1', 'f0']] | ||
assert_raises(ValueError, memoryview, a) | ||
|
||
def test_native_padding_3(self): | ||
dt = np.dtype( | ||
|
@@ -7075,15 +7120,9 @@ def test_intra_padding(self): | |
align = np.dtype('i').alignment | ||
size = np.dtype('i').itemsize | ||
|
||
def aligned(n): | ||
return (align*(1 + (n-1)//align)) | ||
|
||
self._check('(3)T{ix}', (dict( | ||
names=['f0'], | ||
formats=['i'], | ||
offsets=[0], | ||
itemsize=aligned(size + 1) | ||
), (3,))) | ||
expected_dtype = {'names': ['f0'], 'formats': ['i'], | ||
'itemsize': np.dtype('i,V1', align=True).itemsize} | ||
self._check('(3)T{ix}', (expected_dtype, (3,))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this an exact duplicate of the above test? What am I missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh sorry, I fudged the rebase here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although perhaps using |
||
|
||
def test_char_vs_string(self): | ||
dt = np.dtype('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.
I think these tests might remain valid with
T{...}
? In particular, I would expect trailing padding to be kept in a struct context, to match the behaviour ofsizeof(T)
in C, and what happens when structs are repeatedThere 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.
The PEP3118 spec is unclear about this.
One could argue the
struct
module has set no precedent in judging how alignment and padding work here since it doesn't implement theT{}
format. We might then feel free to set the precedent here in this PR, deciding that aligned formats add trailing padding only insideT{}
.Uh oh!
There was an error while loading. Please reload this page.
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.
Whereas the
ctypes
module has set the precedent of ignoring all the remarks that the struct docs make about alignment...