Fix undefined memoryview format (#2223)

* Fix undefined memoryview format

* Add missing <algorithm> header

* Add workaround for py27 array compatibility

* Workaround py27 memoryview behavior

* Fix memoryview constructor from buffer_info

* Workaround PyMemoryView_FromMemory availability in py27

* Fix up memoryview tests

* Update memoryview test from buffer to check signedness

* Use static factory method to create memoryview

* Remove ndim arg from memoryview::frombuffer and add tests

* Allow ndim=0 memoryview and documentation fixup

* Use void* to align to frombuffer method signature

* Add const variants of frombuffer and frommemory

* Add memory view section in doc

* Fix docs

* Add test for null buffer

* Workaround py27 nullptr behavior in test

* Rename frombuffer to from_buffer
This commit is contained in:
Kota Yamaguchi
2020-07-16 00:50:43 +09:00
committed by GitHub
parent aa982e131d
commit e248869893
6 changed files with 293 additions and 27 deletions

View File

@@ -318,4 +318,53 @@ TEST_SUBMODULE(pytypes, m) {
m.def("test_list_slicing", [](py::list a) {
return a[py::slice(0, -1, 2)];
});
m.def("test_memoryview_object", [](py::buffer b) {
return py::memoryview(b);
});
m.def("test_memoryview_buffer_info", [](py::buffer b) {
return py::memoryview(b.request());
});
m.def("test_memoryview_from_buffer", [](bool is_unsigned) {
static const int16_t si16[] = { 3, 1, 4, 1, 5 };
static const uint16_t ui16[] = { 2, 7, 1, 8 };
if (is_unsigned)
return py::memoryview::from_buffer(
ui16, { 4 }, { sizeof(uint16_t) });
else
return py::memoryview::from_buffer(
si16, { 5 }, { sizeof(int16_t) });
});
m.def("test_memoryview_from_buffer_nativeformat", []() {
static const char* format = "@i";
static const int32_t arr[] = { 4, 7, 5 };
return py::memoryview::from_buffer(
arr, sizeof(int32_t), format, { 3 }, { sizeof(int32_t) });
});
m.def("test_memoryview_from_buffer_empty_shape", []() {
static const char* buf = "";
return py::memoryview::from_buffer(buf, 1, "B", { }, { });
});
m.def("test_memoryview_from_buffer_invalid_strides", []() {
static const char* buf = "\x02\x03\x04";
return py::memoryview::from_buffer(buf, 1, "B", { 3 }, { });
});
m.def("test_memoryview_from_buffer_nullptr", []() {
return py::memoryview::from_buffer(
static_cast<void*>(nullptr), 1, "B", { }, { });
});
#if PY_MAJOR_VERSION >= 3
m.def("test_memoryview_from_memory", []() {
const char* buf = "\xff\xe1\xab\x37";
return py::memoryview::from_memory(
buf, static_cast<ssize_t>(strlen(buf)));
});
#endif
}

View File

@@ -278,3 +278,70 @@ def test_number_protocol():
def test_list_slicing():
li = list(range(100))
assert li[::2] == m.test_list_slicing(li)
@pytest.mark.parametrize('method, args, fmt, expected_view', [
(m.test_memoryview_object, (b'red',), 'B', b'red'),
(m.test_memoryview_buffer_info, (b'green',), 'B', b'green'),
(m.test_memoryview_from_buffer, (False,), 'h', [3, 1, 4, 1, 5]),
(m.test_memoryview_from_buffer, (True,), 'H', [2, 7, 1, 8]),
(m.test_memoryview_from_buffer_nativeformat, (), '@i', [4, 7, 5]),
])
def test_memoryview(method, args, fmt, expected_view):
view = method(*args)
assert isinstance(view, memoryview)
assert view.format == fmt
if isinstance(expected_view, bytes) or sys.version_info[0] >= 3:
view_as_list = list(view)
else:
# Using max to pick non-zero byte (big-endian vs little-endian).
view_as_list = [max([ord(c) for c in s]) for s in view]
assert view_as_list == list(expected_view)
@pytest.mark.skipif(
not hasattr(sys, 'getrefcount'),
reason='getrefcount is not available')
@pytest.mark.parametrize('method', [
m.test_memoryview_object,
m.test_memoryview_buffer_info,
])
def test_memoryview_refcount(method):
buf = b'\x0a\x0b\x0c\x0d'
ref_before = sys.getrefcount(buf)
view = method(buf)
ref_after = sys.getrefcount(buf)
assert ref_before < ref_after
assert list(view) == list(buf)
def test_memoryview_from_buffer_empty_shape():
view = m.test_memoryview_from_buffer_empty_shape()
assert isinstance(view, memoryview)
assert view.format == 'B'
if sys.version_info.major < 3:
# Python 2 behavior is weird, but Python 3 (the future) is fine.
assert bytes(view).startswith(b'<memory at ')
else:
assert bytes(view) == b''
def test_test_memoryview_from_buffer_invalid_strides():
with pytest.raises(RuntimeError):
m.test_memoryview_from_buffer_invalid_strides()
def test_test_memoryview_from_buffer_nullptr():
if sys.version_info.major < 3:
m.test_memoryview_from_buffer_nullptr()
else:
with pytest.raises(ValueError):
m.test_memoryview_from_buffer_nullptr()
@pytest.mark.skipif(sys.version_info.major < 3, reason='API not available')
def test_memoryview_from_memory():
view = m.test_memoryview_from_memory()
assert isinstance(view, memoryview)
assert view.format == 'B'
assert bytes(view) == b'\xff\xe1\xab\x37'