Skip to content

ENH: show dtype in array repr when endianness is non-native - #23295

Merged
seberg merged 11 commits into
numpy:mainfrom
molsonkiko:dtype-endian-repr
Mar 14, 2023
Merged

seberg merged 11 commits into
numpy:mainfrom
molsonkiko:dtype-endian-repr

Conversation

@molsonkiko

Copy link
Copy Markdown
Contributor

See #19059.
Goal is to get

>>> import numpy as np
>>> np.array([1], dtype='>u2') # nonstandard endianness should be reflected in repr
array([1], dtype='>u2')
>>> np.array([1], dtype='<u2')
array([1], dtype=uint16)

Try to fix problem where, for example,
np.array([1], dtype='>u2') and np.array([1], dtype='<u2')
both got represented as np.array([1], dtype=uint16)
Comment thread numpy/core/arrayprint.py Outdated
if dtype.names is not None:
return False

if dtype.byteorder != '=' and dtype.itemsize != 1:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are failing, I suspect dtype.isnative should work better?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works much better! I tried running numpy/core/tests/test_arrayprint.py with the changes in my last commit and now all the tests in that file pass.

@seberg pointed out that dtype.isnative could be used in place
    of my previous composite check.
Now all tests in core/tests/test_arrayprint.py pass
    (except the one xfail)
@charris charris changed the title show endianness when nonstandard: address #19059 ENH: show endianness when nonstandard: address #19059 Mar 3, 2023
This reverts commit eeaa2a9.
I wanted to fix the linter errors, but reformatting
with shed didn't help, so I'll just do it manually.
Comment thread numpy/core/arrayprint.py Outdated
# deal with cases like dtype('<u2') that are identical to an
# established dtype (in this case uint16)
# except that they have a different endianness.
return repr(dtype.descr[0][1])

@eric-wieser eric-wieser Mar 4, 2023 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using descr feels like a mistake to me; descr is generally poorly behaved. I think there should be another way to get this information?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into that. descr was a very ad-hoc choice made because it looked like the right kind of thing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the default in this branch looks like using str() or repr() one of them should be right (plausibly both, because complicated dtypes are dealt with earlier).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that all tests pass when using _construction_repr(dtype) in that conditional branch (the current status as of my most recent commit), should I leave as is, or do you think it's better to switch back?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems still very roundabout (maybe it is actually more direct, but using more internals), and str is already being used for structured dtypes?

array(['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'],
dtype='{}')""".format(styp)))

@pytest.mark.parametrize(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this test fail on big-endian systems?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, good point.
I think what I'll do is

  1. rename little_end to native and big_end to non_native
import sys
if sys.byteorder == 'big':
    # flip native and non-native for big-endian systems
    non_native = non_native.replace('>', '<')
    native = native.replace('<', '>')

1. per eric-wieser's suggestion, using
    _construction_repr(dtype) instead of dtype.descr[0][1]
    to determine how to show non-native dtypes
2. Fix tests so they don't crash on big-endian systems.
3. Remove '<U' and '>U' tests because those are coerced
    into '<U1' and '>U1' respectively by np.array.

@tylerjereddy tylerjereddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried testing on one of the compile farm Big Endian machines, but ran into some toolchain issues unrelated to the PR (ancient gcc version, etc.).

Comment thread numpy/core/tests/test_arrayprint.py Outdated
'''
big_dtype = np.dtype(big_end)
little_dtype = np.dtype(little_end)
import sys

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import is already done at the top of the module

Comment thread numpy/core/tests/test_arrayprint.py Outdated
non_native = non_native.replace('>', '<')
native = native.replace('<', '>')
big_dtype = np.dtype(non_native)
little_dtype = np.dtype(native)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose these variable names might get pretty confusing if the test is to be considered agnostic to endianness.

Comment thread numpy/core/tests/test_arrayprint.py Outdated
'(e.g., int32, bool, float64) '
'should not show dtype, but it does'))
# only show difference if > 1 byte
assert_(big_end_repr != little_end_repr or big_dtype.itemsize <= 1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe prefer plain assert for new testing code for the pytest augmentations?

@molsonkiko

molsonkiko commented Mar 10, 2023 •

Copy link
Copy Markdown
Contributor Author

Is there more that I need to do to move this forward, or are you all just occupied with more pressing things right now?

I see that some of the build tests are failing, but I'm really not familiar with the failure modes exhibited, and it's not clear what I would do to address those issues.

@mattip

mattip commented Mar 12, 2023

Copy link
Copy Markdown
Member

close/reopen to restart circleci, which seems to have a temporary failure.

@mattip mattip closed this Mar 12, 2023
@mattip mattip reopened this Mar 12, 2023

@seberg seberg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just retriggered CircleCI once more. We should make sure it is happy, since this might have modified output.

The tests could be simplified a bit, but it's OK if not.

Comment thread numpy/core/tests/test_arrayprint.py Outdated
('float16', '>e'),
('float32', '>f'),
('float64', '>d'),
# ('<U', '>U'), # these get cast to '<U1' and '>U1'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# ('<U', '>U'), # these get cast to '<U1' and '>U1'

I think it is clear enough that U without a number is weird (I would love to just reject it one day for np.dtype(), but not for many dtype= calls where it has a clearer meaning).

non_native = non_native.replace('>', '<')
native = native.replace('<', '>')
non_native_dtype = np.dtype(non_native)
native_dtype = np.dtype(native)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could probably simplify all of this by just starting with only the native above, and then use:

dtype.newbyteorder()

which swaps it by default.

Comment thread numpy/core/tests/test_arrayprint.py Outdated
if non_native_dtype.itemsize > 1:
# if the type is >1 byte, the non-native endian version
# must show endianness.
assert non_native_repr != native_repr,\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I much prefer avoiding \ if possible. In this case, I personally wouldn't care about the assert text anyway (pytest failures tend to print out the code anyway).

@seberg seberg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just going to remove that "U1" logic as well, thanks for the follow up.

Comment thread numpy/core/tests/test_arrayprint.py Resolved
Comment thread numpy/core/tests/test_arrayprint.py Resolved
assert ('dtype' in native_repr) ^ (native_dtype in _typelessdata),\
("an array's repr should show dtype if and only if the type "
'of the array is NOT one of the standard types '
'(e.g., int32, bool, float64).')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer this as an if native_dtype in [np.int_, np.float64, np.complex128, np.bool_] (or _tyeplessdata), but that is just me nitpicking (xor is much harder to reason about).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you'd prefer something like

msg = ("an array's repr should show dtype if and only if the type "
                 'of the array is NOT one of the standard types '
                 '(e.g., int32, bool, float64).')
if native_dtype in _typelessdata:
    assert 'dtype' in native_repr, msg
else:
    assert 'dtype' not in native_repr, msg

I see nothing wrong with that. That doesn't seem much more clear to me, but maybe I'm the only one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it is a bit nicer, especially if the msg was a comment on the branch. But we already cleaned up the test a lot and this doesn't matter enough.

Thanks @molsonkiko it is a nice improvement/fix.

@seberg seberg changed the title ENH: show endianness when nonstandard: address #19059 ENH: show dtype in array repr when endianness is non-native Mar 14, 2023
@seberg
seberg merged commit 071388f into numpy:main Mar 14, 2023
@molsonkiko
molsonkiko deleted the dtype-endian-repr branch March 14, 2023 14:20
pllim added a commit to pllim/astropy that referenced this pull request Mar 17, 2023
to avoid change in printed dtype caused by numpy/numpy#23295
pllim added a commit to pllim/astropy that referenced this pull request Mar 17, 2023
to avoid change in printed dtype caused by numpy/numpy#23295
cmarmo pushed a commit to cmarmo/astropy that referenced this pull request Mar 24, 2023
to avoid change in printed dtype caused by numpy/numpy#23295
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

6 participants