Skip to content

Assignment supports only some bytes-like objects #12075

Open

Description

@aparamon

According to Python Glossary, all of bytes, bytearray, array.array and memoryview support bytes-like interface. However, [:] assignment is supported from all but bytes objects:

import array
import numpy as np

b = bytearray(b'\x00'*8)
arr = np.asarray(b)

arr[:] = bytes(b'\x89HDF\r\n\x1a\n')  # fail
arr[:] = bytearray(b'\x89HDF\r\n\x1a\n')  # success
arr[:] = array.array('B', b'\x89HDF\r\n\x1a\n')  # success
arr[:] = memoryview(b'\x89HDF\r\n\x1a\n')  # success
arr[:] = bytes(b'\x89HDF\r\n\x1a\n')
UnicodeDecodeError: 'ascii' codec can't decode byte 0x89 in position 0: ordinal not in range(128)

It is proposed that numpy.array supports assignment from bytes objects.


Update by seberg (~NumPy 1.23) the error is now:

ValueError: invalid literal for int() with base 10: b'\x89HDF\r\n\x1a\n'

Activity

  1. aparamon commented on Oct 4, 2018

    @aparamon
    Author

    This should make numpy.array a reliable target of readinto().

  2. eric-wieser commented on Oct 4, 2018

    @eric-wieser
  3. eric-wieser commented on Oct 4, 2018

    @eric-wieser
    Member

    Actually, i have no idea if what I said is true. Can you show what arr.dtype is?

  4. aparamon commented on Oct 4, 2018

    @aparamon
    Author

    @eric-wieser arr.dtype is actually uint8, which is in line with my expectations from arr = np.asarray(b) call.

  5. njsmith commented on Oct 4, 2018

    @njsmith
    Member

    Probably at least partly a holdover from python 2, where bytes was str and you definitely wouldn't want to silently unpack it into a sequence of individual bytes.

    In Python 3 this unpacking does feel more natural, but the issue isn't totally gone... Arrays treat bytes as a scalar in lots of contexts, so treating them as a sequence here could be confusing. I'm not sure.

  6. njsmith commented on Oct 4, 2018

    @njsmith
    Member

    For example, I don't think we can make np.asarray(b"hello") start returning an array of uint8. That would be really surprising. But I definitely wouldn't expect the code above to work if you wrote arr[:] = np.asarray(b"hello"), since that's an unambiguous dimension mismatch.

  7. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    No disagreement that np.asarray(b"hello") is expected to fail due to ambiguity. That's why I'm explicitly initializing arr = np.asarray(bytearray(b"hello")) which expectedly and correctly selects dtype = np.uint8.

    My point is that assignment arr[:] = b"hello" should be smart enough to treat the right side as memoryview(b"hello") in this case, i.e. as bytes-like object not null-terminated byte-string.

  8. njsmith commented on Oct 5, 2018

    @njsmith
    Member

    Generally speaking though, arr[:] = ... is equivalent to arr[:] = np.asarray(...). For example:

    In [1]: byte_scalar_array = np.asarray(b"hello")                                
    
    In [2]: byte_scalar_array                                                       
    Out[2]: array(b'hello', dtype='|S5')
    
    In [4]: byte_scalar_array[...] = b"bye"                                         
    
    In [5]: byte_scalar_array                                                       
    Out[5]: array(b'bye', dtype='|S5')

    So should we break this, or should ... assignment do something different depending on the dimension of the target array, or... what?

  9. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    I see no reason why we can't have both

    np.asarray(b"hello")[...] = b'bye'

    and

    np.asarray(bytearray(b"hello"))[...] = b'world'

    depending on the dtype.

  10. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    Very basically, I'm proposing

    def ndarray.__setitem__(self, key, value):
        if self.dtype == np.uint8 and isinstance(value, bytes):
            return ndarray.__setitem__(self, key, memoryview(value))
        ...

    (please do not consider a PR ;-) )

  11. eric-wieser commented on Oct 5, 2018

    @eric-wieser
    Member

    At the very least I think we can produce a more useful error message here. Getting a unicode decode error here is super surprising

  12. njsmith commented on Oct 5, 2018

    @njsmith
    Member

    That's true... why do we get a unicode decode error here?

    I'm very unenthused about the idea of adding (more) dtype-based special-cases to __setattr__ though...

    @aparamon what are you trying to do here by the way? I'm actually somewhat surprised that np.asarray(bytearray(...)) gives an array of uint8 instead of a bytes scalar...

  13. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    np.version.version gives "1.14.3".

    I believe np.asarray() regarding bytearray as array of bytes (np.uint8s) is perfectly logical :-)
    But let's make sure we're not discussing pros and contras of this behavior. I should have probably used

    arr = np.array([0]*8, dtype=np.uint8)

    for more straightforward example.


    Successful assignment into np.ndarray of np.uint8s from different bytes-like objects, followed by failure to assign from actual bytes caught me by large surprise.

    I'm using np.ndarray of np.uint8s as the universal target of binary readinto():
    https://github.com/h5py/h5py/blob/master/h5py/h5fd.pyx#L159
    because np.asarray() appeared to be the only universally supported (among many Pythons, Cythons) way to wrap C bytes buffer for write without memcpy.

  14. njsmith commented on Oct 5, 2018

    @njsmith
    Member

    I feel like memoryview would be a better tool to use there? It's in Python since 2.7, and if anything modern Cython actually prefers that raw byte interfaces use memoryviews instead of ndarray...

  15. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    @njsmith I think you are mistaken: PyMemoryView_FromMemory is only available in Python 3:
    https://docs.python.org/3/c-api/memoryview.html
    np.ndarray seemed a life-saver initially...

  16. njsmith commented on Oct 5, 2018

    @njsmith
    Member

    I'm pretty sure Cython is actually converting your data into a memoryview, before passing it to np.asarray... PyMemoryView_FromMemory may not exist on python 2, but cython rarely lets things like that stop it :-)

  17. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    @njsmith Haha, you were absolutely right! The code works equally well (and even better wrt assignment from bytes) without np.asarray() calls.
    But still, I stay confident that the numpy issue reported should be addressed.

  18. aparamon commented on Oct 5, 2018

    @aparamon
    Author

    My joy was a bit premature: Cython memoryview can't be assigned from bytes too, this time with message:

        b[:len(data)] = data
      File "stringsource", line 418, in View.MemoryView.memoryview.__setitem__
      File "stringsource", line 429, in View.MemoryView.memoryview.is_slice
      File "stringsource", line 348, in View.MemoryView.memoryview.__cinit__
    BufferError: Object is not writable.

    https://stackoverflow.com/a/29067248/751247
    I will submit an issue for Cython too.

  19. eric-wieser commented on Oct 15, 2018

    @eric-wieser
    Member

    @njsmith

    That's true... why do we get a unicode decode error here?

    You can thank 737f690, which seems to work around a python bug. For some reason, float(b'1.0') and int(b'1') are both fine, yet complex(b'1') is not. To work around this, we try to decode to unicode first.

    Note that https://bugs.python.org/issue27572 is trying to deprecate the set of these that do currently work anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions