Skip to content

Commit 4e99a31

Browse files
committed
Issue python#14181: Allow memoryview construction from an object that uses the
getbuffer redirection scheme.
1 parent ab8f392 commit 4e99a31

3 files changed

Lines changed: 105 additions & 13 deletions

File tree

Lib/test/test_buffer.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3373,6 +3373,15 @@ def test_memoryview_release(self):
33733373
del nd
33743374
m.release()
33753375

3376+
a = bytearray([1,2,3])
3377+
m = memoryview(a)
3378+
nd1 = ndarray(m, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3379+
nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3380+
self.assertIs(nd2.obj, m)
3381+
self.assertRaises(BufferError, m.release)
3382+
del nd1, nd2
3383+
m.release()
3384+
33763385
# chained views
33773386
a = bytearray([1,2,3])
33783387
m1 = memoryview(a)
@@ -3383,6 +3392,17 @@ def test_memoryview_release(self):
33833392
del nd
33843393
m2.release()
33853394

3395+
a = bytearray([1,2,3])
3396+
m1 = memoryview(a)
3397+
m2 = memoryview(m1)
3398+
nd1 = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3399+
nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3400+
self.assertIs(nd2.obj, m2)
3401+
m1.release()
3402+
self.assertRaises(BufferError, m2.release)
3403+
del nd1, nd2
3404+
m2.release()
3405+
33863406
# Allow changing layout while buffers are exported.
33873407
nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT)
33883408
m1 = memoryview(nd)
@@ -3418,12 +3438,82 @@ def catch22(b):
34183438
catch22(m1)
34193439
self.assertEqual(m1[0], ord(b'1'))
34203440

3441+
x = ndarray(list(range(12)), shape=[2,2,3], format='l')
3442+
y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3443+
z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3444+
self.assertIs(z.obj, x)
3445+
with memoryview(z) as m:
3446+
catch22(m)
3447+
self.assertEqual(m[0:1].tolist(), [[[0, 1, 2], [3, 4, 5]]])
3448+
3449+
# Test garbage collection.
3450+
for flags in (0, ND_REDIRECT):
3451+
x = bytearray(b'123')
3452+
with memoryview(x) as m1:
3453+
del x
3454+
y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags)
3455+
with memoryview(y) as m2:
3456+
del y
3457+
z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags)
3458+
with memoryview(z) as m3:
3459+
del z
3460+
catch22(m3)
3461+
catch22(m2)
3462+
catch22(m1)
3463+
self.assertEqual(m1[0], ord(b'1'))
3464+
self.assertEqual(m2[1], ord(b'2'))
3465+
self.assertEqual(m3[2], ord(b'3'))
3466+
del m3
3467+
del m2
3468+
del m1
3469+
3470+
x = bytearray(b'123')
3471+
with memoryview(x) as m1:
3472+
del x
3473+
y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags)
3474+
with memoryview(y) as m2:
3475+
del y
3476+
z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags)
3477+
with memoryview(z) as m3:
3478+
del z
3479+
catch22(m1)
3480+
catch22(m2)
3481+
catch22(m3)
3482+
self.assertEqual(m1[0], ord(b'1'))
3483+
self.assertEqual(m2[1], ord(b'2'))
3484+
self.assertEqual(m3[2], ord(b'3'))
3485+
del m1, m2, m3
3486+
34213487
# XXX If m1 has exports, raise BufferError.
34223488
# x = bytearray(b'123')
34233489
# with memoryview(x) as m1:
34243490
# ex = ndarray(m1)
34253491
# m1[0] == ord(b'1')
34263492

3493+
def test_memoryview_redirect(self):
3494+
3495+
nd = ndarray([1.0 * x for x in range(12)], shape=[12], format='d')
3496+
a = array.array('d', [1.0 * x for x in range(12)])
3497+
3498+
for x in (nd, a):
3499+
y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3500+
z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT)
3501+
m = memoryview(z)
3502+
3503+
self.assertIs(y.obj, x)
3504+
self.assertIs(z.obj, x)
3505+
self.assertIs(m.obj, x)
3506+
3507+
self.assertEqual(m, x)
3508+
self.assertEqual(m, y)
3509+
self.assertEqual(m, z)
3510+
3511+
self.assertEqual(m[1:3], x[1:3])
3512+
self.assertEqual(m[1:3], y[1:3])
3513+
self.assertEqual(m[1:3], z[1:3])
3514+
del y, z
3515+
self.assertEqual(m[1:3], x[1:3])
3516+
34273517
def test_issue_7385(self):
34283518
x = ndarray([1,2,3], shape=[3], flags=ND_GETBUF_FAIL)
34293519
self.assertRaises(BufferError, memoryview, x)

Modules/_testbuffer.c

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ static PyTypeObject NDArray_Type;
5353
#define ND_SCALAR 0x008 /* scalar: ndim = 0 */
5454
#define ND_PIL 0x010 /* convert to PIL-style array (suboffsets) */
5555
#define ND_GETBUF_FAIL 0x020 /* test issue 7385 */
56+
#define ND_REDIRECT 0x040 /* redirect buffer requests */
5657

5758
/* Default: NumPy style (strides), read-only, no var-export, C-style layout */
5859
#define ND_DEFAULT 0x0
5960

6061
/* Internal flags for the base buffer */
61-
#define ND_C 0x040 /* C contiguous layout (default) */
62-
#define ND_OWN_ARRAYS 0x080 /* consumer owns arrays */
63-
#define ND_UNUSED 0x100 /* initializer */
62+
#define ND_C 0x080 /* C contiguous layout (default) */
63+
#define ND_OWN_ARRAYS 0x100 /* consumer owns arrays */
6464

6565
/* ndarray properties */
6666
#define ND_IS_CONSUMER(nd) \
@@ -1290,7 +1290,7 @@ ndarray_init(PyObject *self, PyObject *args, PyObject *kwds)
12901290
PyObject *strides = NULL; /* number of bytes to the next elt in each dim */
12911291
Py_ssize_t offset = 0; /* buffer offset */
12921292
PyObject *format = simple_format; /* struct module specifier: "B" */
1293-
int flags = ND_UNUSED; /* base buffer and ndarray flags */
1293+
int flags = ND_DEFAULT; /* base buffer and ndarray flags */
12941294

12951295
int getbuf = PyBUF_UNUSED; /* re-exporter: getbuffer request flags */
12961296

@@ -1302,10 +1302,10 @@ ndarray_init(PyObject *self, PyObject *args, PyObject *kwds)
13021302
/* NDArrayObject is re-exporter */
13031303
if (PyObject_CheckBuffer(v) && shape == NULL) {
13041304
if (strides || offset || format != simple_format ||
1305-
flags != ND_UNUSED) {
1305+
!(flags == ND_DEFAULT || flags == ND_REDIRECT)) {
13061306
PyErr_SetString(PyExc_TypeError,
1307-
"construction from exporter object only takes a single "
1308-
"additional getbuf argument");
1307+
"construction from exporter object only takes 'obj', 'getbuf' "
1308+
"and 'flags' arguments");
13091309
return -1;
13101310
}
13111311

@@ -1315,6 +1315,7 @@ ndarray_init(PyObject *self, PyObject *args, PyObject *kwds)
13151315
return -1;
13161316

13171317
init_flags(nd->head);
1318+
nd->head->flags |= flags;
13181319

13191320
return 0;
13201321
}
@@ -1333,8 +1334,6 @@ ndarray_init(PyObject *self, PyObject *args, PyObject *kwds)
13331334
return -1;
13341335
}
13351336

1336-
if (flags == ND_UNUSED)
1337-
flags = ND_DEFAULT;
13381337
if (flags & ND_VAREXPORT) {
13391338
nd->flags |= ND_VAREXPORT;
13401339
flags &= ~ND_VAREXPORT;
@@ -1357,7 +1356,7 @@ ndarray_push(PyObject *self, PyObject *args, PyObject *kwds)
13571356
PyObject *strides = NULL; /* number of bytes to the next elt in each dim */
13581357
PyObject *format = simple_format; /* struct module specifier: "B" */
13591358
Py_ssize_t offset = 0; /* buffer offset */
1360-
int flags = ND_UNUSED; /* base buffer flags */
1359+
int flags = ND_DEFAULT; /* base buffer flags */
13611360

13621361
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OnOi", kwlist,
13631362
&items, &shape, &strides, &offset, &format, &flags))
@@ -1423,6 +1422,11 @@ ndarray_getbuf(NDArrayObject *self, Py_buffer *view, int flags)
14231422
Py_buffer *base = &ndbuf->base;
14241423
int baseflags = ndbuf->flags;
14251424

1425+
/* redirect mode */
1426+
if (base->obj != NULL && (baseflags&ND_REDIRECT)) {
1427+
return PyObject_GetBuffer(base->obj, view, flags);
1428+
}
1429+
14261430
/* start with complete information */
14271431
*view = *base;
14281432
view->obj = NULL;
@@ -2654,6 +2658,7 @@ PyInit__testbuffer(void)
26542658
PyModule_AddIntConstant(m, "ND_SCALAR", ND_SCALAR);
26552659
PyModule_AddIntConstant(m, "ND_PIL", ND_PIL);
26562660
PyModule_AddIntConstant(m, "ND_GETBUF_FAIL", ND_GETBUF_FAIL);
2661+
PyModule_AddIntConstant(m, "ND_REDIRECT", ND_REDIRECT);
26572662

26582663
PyModule_AddIntConstant(m, "PyBUF_SIMPLE", PyBUF_SIMPLE);
26592664
PyModule_AddIntConstant(m, "PyBUF_WRITABLE", PyBUF_WRITABLE);

Objects/memoryobject.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ _PyManagedBuffer_FromObject(PyObject *base)
9191
return NULL;
9292
}
9393

94-
/* Assume that master.obj is a new reference to base. */
95-
assert(mbuf->master.obj == base);
96-
9794
return (PyObject *)mbuf;
9895
}
9996

0 commit comments

Comments
 (0)