# # The ndarray object from _testbuffer.c is a complete implementation of # a PEP-3118 buffer provider. It is independent from NumPy's ndarray # and the tests don't require NumPy. # # If NumPy is present, some tests check both ndarray implementations # against each other. # # Most ndarray tests also check that memoryview(ndarray) behaves in # the same way as the original. Thus, a substantial part of the # memoryview tests is now in this module. # # Written and designed by Stefan Krah for Python 3.3. # import contextlib import unittest from test import support from test.support import os_helper import inspect from itertools import permutations, product from random import randrange, sample, choice import warnings import sys, array, io, os from decimal import Decimal from fractions import Fraction from test.support import warnings_helper try: from _testbuffer import * except ImportError: ndarray = None try: import struct except ImportError: struct = None try: import ctypes except ImportError: ctypes = None try: with os_helper.EnvironmentVarGuard() as os.environ, \ warnings.catch_warnings(): from numpy import ndarray as numpy_array except ImportError: numpy_array = None try: import _testcapi except ImportError: _testcapi = None SHORT_TEST = True # ====================================================================== # Random lists by format specifier # ====================================================================== # Native format chars and their ranges. NATIVE = { '?':0, 'c':0, 'b':0, 'B':0, 'h':0, 'H':0, 'i':0, 'I':0, 'l':0, 'L':0, 'n':0, 'N':0, 'e':0, 'f':0, 'd':0, 'P':0 } # NumPy does not have 'n' or 'N': if numpy_array: del NATIVE['n'] del NATIVE['N'] if struct: try: # Add "qQ" if present in native mode. struct.pack('Q', 2**64-1) NATIVE['q'] = 0 NATIVE['Q'] = 0 except struct.error: pass # Standard format chars and their ranges. STANDARD = { '?':(0, 2), 'c':(0, 1<<8), 'b':(-(1<<7), 1<<7), 'B':(0, 1<<8), 'h':(-(1<<15), 1<<15), 'H':(0, 1<<16), 'i':(-(1<<31), 1<<31), 'I':(0, 1<<32), 'l':(-(1<<31), 1<<31), 'L':(0, 1<<32), 'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64), 'e':(-65519, 65520), 'f':(-(1<<63), 1<<63), 'd':(-(1<<1023), 1<<1023) } def native_type_range(fmt): """Return range of a native type.""" if fmt == 'c': lh = (0, 256) elif fmt == '?': lh = (0, 2) elif fmt == 'e': lh = (-65519, 65520) elif fmt == 'f': lh = (-(1<<63), 1<<63) elif fmt == 'd': lh = (-(1<<1023), 1<<1023) else: for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7): try: struct.pack(fmt, (1<':STANDARD, '=':STANDARD, '!':STANDARD } if struct: for fmt in fmtdict['@']: fmtdict['@'][fmt] = native_type_range(fmt) # Format codes supported by the memoryview object MEMORYVIEW = NATIVE.copy() # Format codes supported by array.array ARRAY = NATIVE.copy() for k in NATIVE: if not k in "bBhHiIlLfd": del ARRAY[k] BYTEFMT = NATIVE.copy() for k in NATIVE: if not k in "Bbc": del BYTEFMT[k] fmtdict['m'] = MEMORYVIEW fmtdict['@m'] = MEMORYVIEW fmtdict['a'] = ARRAY fmtdict['b'] = BYTEFMT fmtdict['@b'] = BYTEFMT # Capabilities of the test objects: MODE = 0 MULT = 1 cap = { # format chars # multiplier 'ndarray': (['', '@', '<', '>', '=', '!'], ['', '1', '2', '3']), 'array': (['a'], ['']), 'numpy': ([''], ['']), 'memoryview': (['@m', 'm'], ['']), 'bytefmt': (['@b', 'b'], ['']), } def randrange_fmt(mode, char, obj): """Return random item for a type specified by a mode and a single format character.""" x = randrange(*fmtdict[mode][char]) if char == 'c': x = bytes([x]) if obj == 'numpy' and x == b'\x00': # https://github.com/numpy/numpy/issues/2518 x = b'\x01' if char == '?': x = bool(x) if char in 'efd': x = struct.pack(char, x) x = struct.unpack(char, x)[0] return x def gen_item(fmt, obj): """Return single random item.""" mode, chars = fmt.split('#') x = [] for c in chars: x.append(randrange_fmt(mode, c, obj)) return x[0] if len(x) == 1 else tuple(x) def gen_items(n, fmt, obj): """Return a list of random items (or a scalar).""" if n == 0: return gen_item(fmt, obj) lst = [0] * n for i in range(n): lst[i] = gen_item(fmt, obj) return lst def struct_items(n, obj): mode = choice(cap[obj][MODE]) xfmt = mode + '#' fmt = mode.strip('amb') nmemb = randrange(2, 10) # number of struct members for _ in range(nmemb): char = choice(tuple(fmtdict[mode])) multiplier = choice(cap[obj][MULT]) xfmt += (char * int(multiplier if multiplier else 1)) fmt += (multiplier + char) items = gen_items(n, xfmt, obj) item = gen_item(xfmt, obj) return fmt, items, item def randitems(n, obj='ndarray', mode=None, char=None): """Return random format, items, item.""" if mode is None: mode = choice(cap[obj][MODE]) if char is None: char = choice(tuple(fmtdict[mode])) multiplier = choice(cap[obj][MULT]) fmt = mode + '#' + char * int(multiplier if multiplier else 1) items = gen_items(n, fmt, obj) item = gen_item(fmt, obj) fmt = mode.strip('amb') + multiplier + char return fmt, items, item def iter_mode(n, obj='ndarray'): """Iterate through supported mode/char combinations.""" for mode in cap[obj][MODE]: for char in fmtdict[mode]: yield randitems(n, obj, mode, char) def iter_format(nitems, testobj='ndarray'): """Yield (format, items, item) for all possible modes and format characters plus one random compound format string.""" for t in iter_mode(nitems, testobj): yield t if testobj != 'ndarray': return yield struct_items(nitems, testobj) def is_byte_format(fmt): return 'c' in fmt or 'b' in fmt or 'B' in fmt def is_memoryview_format(fmt): """format suitable for memoryview""" x = len(fmt) return ((x == 1 or (x == 2 and fmt[0] == '@')) and fmt[x-1] in MEMORYVIEW) NON_BYTE_FORMAT = [c for c in fmtdict['@'] if not is_byte_format(c)] # ====================================================================== # Multi-dimensional tolist(), slicing and slice assignments # ====================================================================== def atomp(lst): """Tuple items (representing structs) are regarded as atoms.""" return not isinstance(lst, list) def listp(lst): return isinstance(lst, list) def prod(lst): """Product of list elements.""" if len(lst) == 0: return 0 x = lst[0] for v in lst[1:]: x *= v return x def strides_from_shape(ndim, shape, itemsize, layout): """Calculate strides of a contiguous array. Layout is 'C' or 'F' (Fortran).""" if ndim == 0: return () if layout == 'C': strides = list(shape[1:]) + [itemsize] for i in range(ndim-2, -1, -1): strides[i] *= strides[i+1] else: strides = [itemsize] + list(shape[:-1]) for i in range(1, ndim): strides[i] *= strides[i-1] return strides def _ca(items, s): """Convert flat item list to the nested list representation of a multidimensional C array with shape 's'.""" if atomp(items): return items if len(s) == 0: return items[0] lst = [0] * s[0] stride = len(items) // s[0] if s[0] else 0 for i in range(s[0]): start = i*stride lst[i] = _ca(items[start:start+stride], s[1:]) return lst def _fa(items, s): """Convert flat item list to the nested list representation of a multidimensional Fortran array with shape 's'.""" if atomp(items): return items if len(s) == 0: return items[0] lst = [0] * s[0] stride = s[0] for i in range(s[0]): lst[i] = _fa(items[i::stride], s[1:]) return lst def carray(items, shape): if listp(items) and not 0 in shape and prod(shape) != len(items): raise ValueError("prod(shape) != len(items)") return _ca(items, shape) def farray(items, shape): if listp(items) and not 0 in shape and prod(shape) != len(items): raise ValueError("prod(shape) != len(items)") return _fa(items, shape) def indices(shape): """Generate all possible tuples of indices.""" iterables = [range(v) for v in shape] return product(*iterables) def getindex(ndim, ind, strides): """Convert multi-dimensional index to the position in the flat list.""" ret = 0 for i in range(ndim): ret += strides[i] * ind[i] return ret def transpose(src, shape): """Transpose flat item list that is regarded as a multi-dimensional matrix defined by shape: dest...[k][j][i] = src[i][j][k]... """ if not shape: return src ndim = len(shape) sstrides = strides_from_shape(ndim, shape, 1, 'C') dstrides = strides_from_shape(ndim, shape[::-1], 1, 'C') dest = [0] * len(src) for ind in indices(shape): fr = getindex(ndim, ind, sstrides) to = getindex(ndim, ind[::-1], dstrides) dest[to] = src[fr] return dest def _flatten(lst): """flatten list""" if lst == []: return lst if atomp(lst): return [lst] return _flatten(lst[0]) + _flatten(lst[1:]) def flatten(lst): """flatten list or return scalar""" if atomp(lst): # scalar return lst return _flatten(lst) def slice_shape(lst, slices): """Get the shape of lst after slicing: slices is a list of slice objects.""" if atomp(lst): return [] return [len(lst[slices[0]])] + slice_shape(lst[0], slices[1:]) def multislice(lst, slices): """Multi-dimensional slicing: slices is a list of slice objects.""" if atomp(lst): return lst return [multislice(sublst, slices[1:]) for sublst in lst[slices[0]]] def m_assign(llst, rlst, lslices, rslices): """Multi-dimensional slice assignment: llst and rlst are the operands, lslices and rslices are lists of slice objects. llst and rlst must have the same structure. For a two-dimensional example, this is not implemented in Python: llst[0:3:2, 0:3:2] = rlst[1:3:1, 1:3:1] Instead we write: lslices = [slice(0,3,2), slice(0,3,2)] rslices = [slice(1,3,1), slice(1,3,1)] multislice_assign(llst, rlst, lslices, rslices) """ if atomp(rlst): return rlst rlst = [m_assign(l, r, lslices[1:], rslices[1:]) for l, r in zip(llst[lslices[0]], rlst[rslices[0]])] llst[lslices[0]] = rlst return llst def cmp_structure(llst, rlst, lslices, rslices): """Compare the structure of llst[lslices] and rlst[rslices].""" lshape = slice_shape(llst, lslices) rshape = slice_shape(rlst, rslices) if (len(lshape) != len(rshape)): return -1 for i in range(len(lshape)): if lshape[i] != rshape[i]: return -1 if lshape[i] == 0: return 0 return 0 def multislice_assign(llst, rlst, lslices, rslices): """Return llst after assigning: llst[lslices] = rlst[rslices]""" if cmp_structure(llst, rlst, lslices, rslices) < 0: raise ValueError("lvalue and rvalue have different structures") return m_assign(llst, rlst, lslices, rslices) # ====================================================================== # Random structures # ====================================================================== # # PEP-3118 is very permissive with respect to the contents of a # Py_buffer. In particular: # # - shape can be zero # - strides can be any integer, including zero # - offset can point to any location in the underlying # memory block, provided that it is a multiple of # itemsize. # # The functions in this section test and verify random structures # in full generality. A structure is valid iff it fits in the # underlying memory block. # # The structure 't' (short for 'tuple') is fully defined by: # # t = (memlen, itemsize, ndim, shape, strides, offset) # def verify_structure(memlen, itemsize, ndim, shape, strides, offset): """Verify that the parameters represent a valid array within the bounds of the allocated memory: char *mem: start of the physical memory block memlen: length of the physical memory block offset: (char *)buf - mem """ if offset % itemsize: return False if offset < 0 or offset+itemsize > memlen: return False if any(v % itemsize for v in strides): return False if ndim <= 0: return ndim == 0 and not shape and not strides if 0 in shape: return True imin = sum(strides[j]*(shape[j]-1) for j in range(ndim) if strides[j] <= 0) imax = sum(strides[j]*(shape[j]-1) for j in range(ndim) if strides[j] > 0) return 0 <= offset+imin and offset+imax+itemsize <= memlen def get_item(lst, indices): for i in indices: lst = lst[i] return lst def memory_index(indices, t): """Location of an item in the underlying memory.""" memlen, itemsize, ndim, shape, strides, offset = t p = offset for i in range(ndim): p += strides[i]*indices[i] return p def is_overlapping(t): """The structure 't' is overlapping if at least one memory location is visited twice while iterating through all possible tuples of indices.""" memlen, itemsize, ndim, shape, strides, offset = t visited = 1<= 95 and valid: minshape = 0 elif n >= 90: minshape = 1 shape = [0] * ndim for i in range(ndim): shape[i] = randrange(minshape, maxshape+1) else: ndim = len(shape) maxstride = 5 n = randrange(100) zero_stride = True if n >= 95 and n & 1 else False strides = [0] * ndim strides[ndim-1] = itemsize * randrange(-maxstride, maxstride+1) if not zero_stride and strides[ndim-1] == 0: strides[ndim-1] = itemsize for i in range(ndim-2, -1, -1): maxstride *= shape[i+1] if shape[i+1] else 1 if zero_stride: strides[i] = itemsize * randrange(-maxstride, maxstride+1) else: strides[i] = ((1,-1)[randrange(2)] * itemsize * randrange(1, maxstride+1)) imin = imax = 0 if not 0 in shape: imin = sum(strides[j]*(shape[j]-1) for j in range(ndim) if strides[j] <= 0) imax = sum(strides[j]*(shape[j]-1) for j in range(ndim) if strides[j] > 0) nitems = imax - imin if valid: offset = -imin * itemsize memlen = offset + (imax+1) * itemsize else: memlen = (-imin + imax) * itemsize offset = -imin-itemsize if randrange(2) == 0 else memlen return memlen, itemsize, ndim, shape, strides, offset def randslice_from_slicelen(slicelen, listlen): """Create a random slice of len slicelen that fits into listlen.""" maxstart = listlen - slicelen start = randrange(maxstart+1) maxstep = (listlen - start) // slicelen if slicelen else 1 step = randrange(1, maxstep+1) stop = start + slicelen * step s = slice(start, stop, step) _, _, _, control = slice_indices(s, listlen) if control != slicelen: raise RuntimeError return s def randslice_from_shape(ndim, shape): """Create two sets of slices for an array x with shape 'shape' such that shapeof(x[lslices]) == shapeof(x[rslices]).""" lslices = [0] * ndim rslices = [0] * ndim for n in range(ndim): l = shape[n] slicelen = randrange(1, l+1) if l > 0 else 0 lslices[n] = randslice_from_slicelen(slicelen, l) rslices[n] = randslice_from_slicelen(slicelen, l) return tuple(lslices), tuple(rslices) def rand_aligned_slices(maxdim=5, maxshape=16): """Create (lshape, rshape, tuple(lslices), tuple(rslices)) such that shapeof(x[lslices]) == shapeof(y[rslices]), where x is an array with shape 'lshape' and y is an array with shape 'rshape'.""" ndim = randrange(1, maxdim+1) minshape = 2 n = randrange(100) if n >= 95: minshape = 0 elif n >= 90: minshape = 1 all_random = True if randrange(100) >= 80 else False lshape = [0]*ndim; rshape = [0]*ndim lslices = [0]*ndim; rslices = [0]*ndim for n in range(ndim): small = randrange(minshape, maxshape+1) big = randrange(minshape, maxshape+1) if big < small: big, small = small, big # Create a slice that fits the smaller value. if all_random: start = randrange(-small, small+1) stop = randrange(-small, small+1) step = (1,-1)[randrange(2)] * randrange(1, small+2) s_small = slice(start, stop, step) _, _, _, slicelen = slice_indices(s_small, small) else: slicelen = randrange(1, small+1) if small > 0 else 0 s_small = randslice_from_slicelen(slicelen, small) # Create a slice of the same length for the bigger value. s_big = randslice_from_slicelen(slicelen, big) if randrange(2) == 0: rshape[n], lshape[n] = big, small rslices[n], lslices[n] = s_big, s_small else: rshape[n], lshape[n] = small, big rslices[n], lslices[n] = s_small, s_big return lshape, rshape, tuple(lslices), tuple(rslices) def randitems_from_structure(fmt, t): """Return a list of random items for structure 't' with format 'fmtchar'.""" memlen, itemsize, _, _, _, _ = t return gen_items(memlen//itemsize, '#'+fmt, 'numpy') def ndarray_from_structure(items, fmt, t, flags=0): """Return ndarray from the tuple returned by rand_structure()""" memlen, itemsize, ndim, shape, strides, offset = t return ndarray(items, shape=shape, strides=strides, format=fmt, offset=offset, flags=ND_WRITABLE|flags) def numpy_array_from_structure(items, fmt, t): """Return numpy_array from the tuple returned by rand_structure()""" memlen, itemsize, ndim, shape, strides, offset = t buf = bytearray(memlen) for j, v in enumerate(items): struct.pack_into(fmt, buf, j*itemsize, v) return numpy_array(buffer=buf, shape=shape, strides=strides, dtype=fmt, offset=offset) # ====================================================================== # memoryview casts # ====================================================================== def cast_items(exporter, fmt, itemsize, shape=None): """Interpret the raw memory of 'exporter' as a list of items with size 'itemsize'. If shape=None, the new structure is assumed to be 1-D with n * itemsize = bytelen. If shape is given, the usual constraint for contiguous arrays prod(shape) * itemsize = bytelen applies. On success, return (items, shape). If the constraints cannot be met, return (None, None). If a chunk of bytes is interpreted as NaN as a result of float conversion, return ('nan', None).""" bytelen = exporter.nbytes if shape: if prod(shape) * itemsize != bytelen: return None, shape elif shape == []: if exporter.ndim == 0 or itemsize != bytelen: return None, shape else: n, r = divmod(bytelen, itemsize) shape = [n] if r != 0: return None, shape mem = exporter.tobytes() byteitems = [mem[i:i+itemsize] for i in range(0, len(mem), itemsize)] items = [] for v in byteitems: item = struct.unpack(fmt, v)[0] if item != item: return 'nan', shape items.append(item) return (items, shape) if shape != [] else (items[0], shape) def gencastshapes(): """Generate shapes to test casting.""" for n in range(32): yield [n] ndim = randrange(4, 6) minshape = 1 if randrange(100) > 80 else 2 yield [randrange(minshape, 5) for _ in range(ndim)] ndim = randrange(2, 4) minshape = 1 if randrange(100) > 80 else 2 yield [randrange(minshape, 5) for _ in range(ndim)] # ====================================================================== # Actual tests # ====================================================================== def genslices(n): """Generate all possible slices for a single dimension.""" return product(range(-n, n+1), range(-n, n+1), range(-n, n+1)) def genslices_ndim(ndim, shape): """Generate all possible slice tuples for 'shape'.""" iterables = [genslices(shape[n]) for n in range(ndim)] return product(*iterables) def rslice(n, allow_empty=False): """Generate random slice for a single dimension of length n. If zero=True, the slices may be empty, otherwise they will be non-empty.""" minlen = 0 if allow_empty or n == 0 else 1 slicelen = randrange(minlen, n+1) return randslice_from_slicelen(slicelen, n) def rslices(n, allow_empty=False): """Generate random slices for a single dimension.""" for _ in range(5): yield rslice(n, allow_empty) def rslices_ndim(ndim, shape, iterations=5): """Generate random slice tuples for 'shape'.""" # non-empty slices for _ in range(iterations): yield tuple(rslice(shape[n]) for n in range(ndim)) # possibly empty slices for _ in range(iterations): yield tuple(rslice(shape[n], allow_empty=True) for n in range(ndim)) # invalid slices yield tuple(slice(0,1,0) for _ in range(ndim)) def rpermutation(iterable, r=None): pool = tuple(iterable) r = len(pool) if r is None else r yield tuple(sample(pool, r)) def ndarray_print(nd): """Print ndarray for debugging.""" try: x = nd.tolist() except (TypeError, NotImplementedError): x = nd.tobytes() if isinstance(nd, ndarray): offset = nd.offset flags = nd.flags else: offset = 'unknown' flags = 'unknown' print("ndarray(%s, shape=%s, strides=%s, suboffsets=%s, offset=%s, " "format='%s', itemsize=%s, flags=%s)" % (x, nd.shape, nd.strides, nd.suboffsets, offset, nd.format, nd.itemsize, flags)) sys.stdout.flush() ITERATIONS = 100 MAXDIM = 5 MAXSHAPE = 10 if SHORT_TEST: ITERATIONS = 10 MAXDIM = 3 MAXSHAPE = 4 genslices = rslices genslices_ndim = rslices_ndim permutations = rpermutation @unittest.skipUnless(struct, 'struct module required for this test.') @unittest.skipUnless(ndarray, 'ndarray object required for this test') class TestBufferProtocol(unittest.TestCase): def setUp(self): # The suboffsets tests need sizeof(void *). self.sizeof_void_p = get_sizeof_void_p() def verify(self, result, *, obj, itemsize, fmt, readonly, ndim, shape, strides, lst, sliced=False, cast=False): # Verify buffer contents against expected values. if shape: expected_len = prod(shape)*itemsize else: if not fmt: # array has been implicitly cast to unsigned bytes expected_len = len(lst) else: # ndim = 0 expected_len = itemsize # Reconstruct suboffsets from strides. Support for slicing # could be added, but is currently only needed for test_getbuf(). suboffsets = () if result.suboffsets: self.assertGreater(ndim, 0) suboffset0 = 0 for n in range(1, ndim): if shape[n] == 0: break if strides[n] <= 0: suboffset0 += -strides[n] * (shape[n]-1) suboffsets = [suboffset0] + [-1 for v in range(ndim-1)] # Not correct if slicing has occurred in the first dimension. stride0 = self.sizeof_void_p if strides[0] < 0: stride0 = -stride0 strides = [stride0] + list(strides[1:]) self.assertIs(result.obj, obj) self.assertEqual(result.nbytes, expected_len) self.assertEqual(result.itemsize, itemsize) self.assertEqual(result.format, fmt) self.assertIs(result.readonly, readonly) self.assertEqual(result.ndim, ndim) self.assertEqual(result.shape, tuple(shape)) if not (sliced and suboffsets): self.assertEqual(result.strides, tuple(strides)) self.assertEqual(result.suboffsets, tuple(suboffsets)) if isinstance(result, ndarray) or is_memoryview_format(fmt): rep = result.tolist() if fmt else result.tobytes() self.assertEqual(rep, lst) if not fmt: # array has been cast to unsigned bytes, return # the remaining tests won't work. # PyBuffer_GetPointer() is the definition how to access an item. # If PyBuffer_GetPointer(indices) is correct for all possible # combinations of indices, the buffer is correct. # # Also test tobytes() against the flattened 'lst', with all items # packed to bytes. if not cast: # casts chop up 'lst' in different ways b = bytearray() buf_err = None for ind in indices(shape): try: item1 = get_pointer(result, ind) item2 = get_item(lst, ind) if isinstance(item2, tuple): x = struct.pack(fmt, *item2) else: x = struct.pack(fmt, item2) b.extend(x) except BufferError: buf_err = True # re-exporter does not provide full buffer break self.assertEqual(item1, item2) if not buf_err: # test tobytes() self.assertEqual(result.tobytes(), b) # test hex() m = memoryview(result) h = "".join("%02x" % c for c in b) self.assertEqual(m.hex(), h) # lst := expected multi-dimensional logical representation # flatten(lst) := elements in C-order ff = fmt if fmt else 'B' flattened = flatten(lst) # Rules for 'A': if the array is already contiguous, return # the array unaltered. Otherwise, return a contiguous 'C' # representation. for order in ['C', 'F', 'A']: expected = result if order == 'F': if not is_contiguous(result, 'A') or \ is_contiguous(result, 'C'): # For constructing the ndarray, convert the # flattened logical representation to Fortran order. trans = transpose(flattened, shape) expected = ndarray(trans, shape=shape, format=ff, flags=ND_FORTRAN) else: # 'C', 'A' if not is_contiguous(result, 'A') or \ is_contiguous(result, 'F') and order == 'C': # The flattened list is already in C-order. expected = ndarray(flattened, shape=shape, format=ff) contig = get_contiguous(result, PyBUF_READ, order) self.assertEqual(contig.tobytes(), b) self.assertTrue(cmp_contig(contig, expected)) if ndim == 0: continue nmemb = len(flattened) ro = 0 if readonly else ND_WRITABLE ### See comment in test_py_buffer_to_contiguous for an ### explanation why these tests are valid. # To 'C' contig = py_buffer_to_contiguous(result, 'C', PyBUF_FULL_RO) self.assertEqual(len(contig), nmemb * itemsize) initlst = [struct.unpack_from(fmt, contig, n*itemsize) for n in range(nmemb)] if len(initlst[0]) == 1: initlst = [v[0] for v in initlst] y = ndarray(initlst, shape=shape, flags=ro, format=fmt) self.assertEqual(memoryview(y), memoryview(result)) contig_bytes = memoryview(result).tobytes() self.assertEqual(contig_bytes, contig) contig_bytes = memoryview(result).tobytes(order=None) self.assertEqual(contig_bytes, contig) contig_bytes = memoryview(result).tobytes(order='C') self.assertEqual(contig_bytes, contig) # To 'F' contig = py_buffer_to_contiguous(result, 'F', PyBUF_FULL_RO) self.assertEqual(len(contig), nmemb * itemsize) initlst = [struct.unpack_from(fmt, contig, n*itemsize) for n in range(nmemb)] if len(initlst[0]) == 1: initlst = [v[0] for v in initlst] y = ndarray(initlst, shape=shape, flags=ro|ND_FORTRAN, format=fmt) self.assertEqual(memoryview(y), memoryview(result)) contig_bytes = memoryview(result).tobytes(order='F') self.assertEqual(contig_bytes, contig) # To 'A' contig = py_buffer_to_contiguous(result, 'A', PyBUF_FULL_RO) self.assertEqual(len(contig), nmemb * itemsize) initlst = [struct.unpack_from(fmt, contig, n*itemsize) for n in range(nmemb)] if len(initlst[0]) == 1: initlst = [v[0] for v in initlst] f = ND_FORTRAN if is_contiguous(result, 'F') else 0 y = ndarray(initlst, shape=shape, flags=f|ro, format=fmt) self.assertEqual(memoryview(y), memoryview(result)) contig_bytes = memoryview(result).tobytes(order='A') self.assertEqual(contig_bytes, contig) if is_memoryview_format(fmt): try: m = memoryview(result) except BufferError: # re-exporter does not provide full information return ex = result.obj if isinstance(result, memoryview) else result def check_memoryview(m, expected_readonly=readonly): self.assertIs(m.obj, ex) self.assertEqual(m.nbytes, expected_len) self.assertEqual(m.itemsize, itemsize) self.assertEqual(m.format, fmt) self.assertEqual(m.readonly, expected_readonly) self.assertEqual(m.ndim, ndim) self.assertEqual(m.shape, tuple(shape)) if not (sliced and suboffsets): self.assertEqual(m.strides, tuple(strides)) self.assertEqual(m.suboffsets, tuple(suboffsets)) if ndim == 0: self.assertRaises(TypeError, len, m) else: self.assertEqual(len(m), len(lst)) rep = result.tolist() if fmt else result.tobytes() self.assertEqual(rep, lst) self.assertEqual(m, result) check_memoryview(m) with m.toreadonly() as mm: check_memoryview(mm, expected_readonly=True) m.tobytes() # Releasing mm didn't release m def verify_getbuf(self, orig_ex, ex, req, sliced=False): def match(req, flag): return ((req&flag) == flag) if (# writable request to read-only exporter (ex.readonly and match(req, PyBUF_WRITABLE)) or # cannot match explicit contiguity request (match(req, PyBUF_C_CONTIGUOUS) and not ex.c_contiguous) or (match(req, PyBUF_F_CONTIGUOUS) and not ex.f_contiguous) or (match(req, PyBUF_ANY_CONTIGUOUS) and not ex.contiguous) or # buffer needs suboffsets (not match(req, PyBUF_INDIRECT) and ex.suboffsets) or # buffer without strides must be C-contiguous (not match(req, PyBUF_STRIDES) and not ex.c_contiguous) or # PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT (not match(req, PyBUF_ND) and match(req, PyBUF_FORMAT))): self.assertRaises(BufferError, ndarray, ex, getbuf=req) return if isinstance(ex, ndarray) or is_memoryview_format(ex.format): lst = ex.tolist() else: nd = ndarray(ex, getbuf=PyBUF_FULL_RO) lst = nd.tolist() # The consumer may have requested default values or a NULL format. ro = False if match(req, PyBUF_WRITABLE) else ex.readonly fmt = ex.format itemsize = ex.itemsize ndim = ex.ndim if not match(req, PyBUF_FORMAT): # itemsize refers to the original itemsize before the cast. # The equality product(shape) * itemsize = len still holds. # The equality calcsize(format) = itemsize does _not_ hold. fmt = '' lst = orig_ex.tobytes() # Issue 12834 if not match(req, PyBUF_ND): ndim = 1 shape = orig_ex.shape if match(req, PyBUF_ND) else () strides = orig_ex.strides if match(req, PyBUF_STRIDES) else () nd = ndarray(ex, getbuf=req) self.verify(nd, obj=ex, itemsize=itemsize, fmt=fmt, readonly=ro, ndim=ndim, shape=shape, strides=strides, lst=lst, sliced=sliced) @support.requires_resource('cpu') def test_ndarray_getbuf(self): requests = ( # distinct flags PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND, PyBUF_SIMPLE, PyBUF_C_CONTIGUOUS, PyBUF_F_CONTIGUOUS, PyBUF_ANY_CONTIGUOUS, # compound requests PyBUF_FULL, PyBUF_FULL_RO, PyBUF_RECORDS, PyBUF_RECORDS_RO, PyBUF_STRIDED, PyBUF_STRIDED_RO, PyBUF_CONTIG, PyBUF_CONTIG_RO, ) # items and format items_fmt = ( ([True if x % 2 else False for x in range(12)], '?'), ([1,2,3,4,5,6,7,8,9,10,11,12], 'b'), ([1,2,3,4,5,6,7,8,9,10,11,12], 'B'), ([(2**31-x) if x % 2 else (-2**31+x) for x in range(12)], 'l') ) # shape, strides, offset structure = ( ([], [], 0), ([1,3,1], [], 0), ([12], [], 0), ([12], [-1], 11), ([6], [2], 0), ([6], [-2], 11), ([3, 4], [], 0), ([3, 4], [-4, -1], 11), ([2, 2], [4, 1], 4), ([2, 2], [-4, -1], 8) ) # ndarray creation flags ndflags = ( 0, ND_WRITABLE, ND_FORTRAN, ND_FORTRAN|ND_WRITABLE, ND_PIL, ND_PIL|ND_WRITABLE ) # flags that can actually be used as flags real_flags = (0, PyBUF_WRITABLE, PyBUF_FORMAT, PyBUF_WRITABLE|PyBUF_FORMAT) for items, fmt in items_fmt: itemsize = struct.calcsize(fmt) for shape, strides, offset in structure: strides = [v * itemsize for v in strides] offset *= itemsize for flags in ndflags: if strides and (flags&ND_FORTRAN): continue if not shape and (flags&ND_PIL): continue _items = items if shape else items[0] ex1 = ndarray(_items, format=fmt, flags=flags, shape=shape, strides=strides, offset=offset) ex2 = ex1[::-2] if shape else None m1 = memoryview(ex1) if ex2: m2 = memoryview(ex2) if ex1.ndim == 0 or (ex1.ndim == 1 and shape and strides): self.assertEqual(m1, ex1) if ex2 and ex2.ndim == 1 and shape and strides: self.assertEqual(m2, ex2) for req in requests: for bits in real_flags: self.verify_getbuf(ex1, ex1, req|bits) self.verify_getbuf(ex1, m1, req|bits) if ex2: self.verify_getbuf(ex2, ex2, req|bits, sliced=True) self.verify_getbuf(ex2, m2, req|bits, sliced=True) items = [1,2,3,4,5,6,7,8,9,10,11,12] # ND_GETBUF_FAIL ex = ndarray(items, shape=[12], flags=ND_GETBUF_FAIL) self.assertRaises(BufferError, ndarray, ex) # Request complex structure from a simple exporter. In this # particular case the test object is not PEP-3118 compliant. base = ndarray([9], [1]) ex = ndarray(base, getbuf=PyBUF_SIMPLE) self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_WRITABLE) self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ND) self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_STRIDES) self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_C_CONTIGUOUS) self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_F_CONTIGUOUS) self.assertRaises(BufferError, ndarray, ex, getbuf=PyBUF_ANY_CONTIGUOUS) nd = ndarray(ex, getbuf=PyBUF_SIMPLE) # Issue #22445: New precise contiguity definition. for shape in [1,12,1], [7,0,7]: for order in 0, ND_FORTRAN: ex = ndarray(items, shape=shape, flags=order|ND_WRITABLE) self.assertTrue(is_contiguous(ex, 'F')) self.assertTrue(is_contiguous(ex, 'C')) for flags in requests: nd = ndarray(ex, getbuf=flags) self.assertTrue(is_contiguous(nd, 'F')) self.assertTrue(is_contiguous(nd, 'C')) def test_ndarray_exceptions(self): nd = ndarray([9], [1]) ndm = ndarray([9], [1], flags=ND_VAREXPORT) # Initialization of a new ndarray or mutation of an existing array. for c in (ndarray, nd.push, ndm.push): # Invalid types. self.assertRaises(TypeError, c, {1,2,3}) self.assertRaises(TypeError, c, [1,2,'3']) self.assertRaises(TypeError, c, [1,2,(3,4)]) self.assertRaises(TypeError, c, [1,2,3], shape={3}) self.assertRaises(TypeError, c, [1,2,3], shape=[3], strides={1}) self.assertRaises(TypeError, c, [1,2,3], shape=[3], offset=[]) self.assertRaises(TypeError, c, [1], shape=[1], format={}) self.assertRaises(TypeError, c, [1], shape=[1], flags={}) self.assertRaises(TypeError, c, [1], shape=[1], getbuf={}) # ND_FORTRAN flag is only valid without strides. self.assertRaises(TypeError, c, [1], shape=[1], strides=[1], flags=ND_FORTRAN) # ND_PIL flag is only valid with ndim > 0. self.assertRaises(TypeError, c, [1], shape=[], flags=ND_PIL) # Invalid items. self.assertRaises(ValueError, c, [], shape=[1]) self.assertRaises(ValueError, c, ['XXX'], shape=[1], format="L") # Invalid combination of items and format. self.assertRaises(struct.error, c, [1000], shape=[1], format="B") self.assertRaises(ValueError, c, [1,(2,3)], shape=[2], format="B") self.assertRaises(ValueError, c, [1,2,3], shape=[3], format="QL") # Invalid ndim. n = ND_MAX_NDIM+1 self.assertRaises(ValueError, c, [1]*n, shape=[1]*n) # Invalid shape. self.assertRaises(ValueError, c, [1], shape=[-1]) self.assertRaises(ValueError, c, [1,2,3], shape=['3']) self.assertRaises(OverflowError, c, [1], shape=[2**128]) # prod(shape) * itemsize != len(items) self.assertRaises(ValueError, c, [1,2,3,4,5], shape=[2,2], offset=3) # Invalid strides. self.assertRaises(ValueError, c, [1,2,3], shape=[3], strides=['1']) self.assertRaises(OverflowError, c, [1], shape=[1], strides=[2**128]) # Invalid combination of strides and shape. self.assertRaises(ValueError, c, [1,2], shape=[2,1], strides=[1]) # Invalid combination of strides and format. self.assertRaises(ValueError, c, [1,2,3,4], shape=[2], strides=[3], format="L") # Invalid offset. self.assertRaises(ValueError, c, [1,2,3], shape=[3], offset=4) self.assertRaises(ValueError, c, [1,2,3], shape=[1], offset=3, format="L") # Invalid format. self.assertRaises(ValueError, c, [1,2,3], shape=[3], format="") self.assertRaises(struct.error, c, [(1,2,3)], shape=[1], format="@#$") # Striding out of the memory bounds. items = [1,2,3,4,5,6,7,8,9,10] self.assertRaises(ValueError, c, items, shape=[2,3], strides=[-3, -2], offset=5) # Constructing consumer: format argument invalid. self.assertRaises(TypeError, c, bytearray(), format="Q") # Constructing original base object: getbuf argument invalid. self.assertRaises(TypeError, c, [1], shape=[1], getbuf=PyBUF_FULL) # Shape argument is mandatory for original base objects. self.assertRaises(TypeError, c, [1]) # PyBUF_WRITABLE request to read-only provider. self.assertRaises(BufferError, ndarray, b'123', getbuf=PyBUF_WRITABLE) # ND_VAREXPORT can only be specified during construction. nd = ndarray([9], [1], flags=ND_VAREXPORT) self.assertRaises(ValueError, nd.push, [1], [1], flags=ND_VAREXPORT) # Invalid operation for consumers: push/pop nd = ndarray(b'123') self.assertRaises(BufferError, nd.push, [1], [1]) self.assertRaises(BufferError, nd.pop) # ND_VAREXPORT not set: push/pop fail with exported buffers nd = ndarray([9], [1]) nd.push([1], [1]) m = memoryview(nd) self.assertRaises(BufferError, nd.push, [1], [1]) self.assertRaises(BufferError, nd.pop) m.release() nd.pop() # Single remaining buffer: pop fails self.assertRaises(BufferError, nd.pop) del nd # get_pointer() self.assertRaises(TypeError, get_pointer, {}, [1,2,3]) self.assertRaises(TypeError, get_pointer, b'123', {}) nd = ndarray(list(range(100)), shape=[1]*100) self.assertRaises(ValueError, get_pointer, nd, [5]) nd = ndarray(list(range(12)), shape=[3,4]) self.assertRaises(ValueError, get_pointer, nd, [2,3,4]) self.assertRaises(ValueError, get_pointer, nd, [3,3]) self.assertRaises(ValueError, get_pointer, nd, [-3,3]) self.assertRaises(OverflowError, get_pointer, nd, [1<<64,3]) # tolist() needs format ex = ndarray([1,2,3], shape=[3], format='L') nd = ndarray(ex, getbuf=PyBUF_SIMPLE) self.assertRaises(ValueError, nd.tolist) # memoryview_from_buffer() ex1 = ndarray([1,2,3], shape=[3], format='L') ex2 = ndarray(ex1) nd = ndarray(ex2) self.assertRaises(TypeError, nd.memoryview_from_buffer) nd = ndarray([(1,)*200], shape=[1], format='L'*200) self.assertRaises(TypeError, nd.memoryview_from_buffer) n = ND_MAX_NDIM nd = ndarray(list(range(n)), shape=[1]*n) self.assertRaises(ValueError, nd.memoryview_from_buffer) # get_contiguous() nd = ndarray([1], shape=[1]) self.assertRaises(TypeError, get_contiguous, 1, 2, 3, 4, 5) self.assertRaises(TypeError, get_contiguous, nd, "xyz", 'C') self.assertRaises(OverflowError, get_contiguous, nd, 2**64, 'C') self.assertRaises(TypeError, get_contiguous, nd, PyBUF_READ, 961) self.assertRaises(UnicodeEncodeError, get_contiguous, nd, PyBUF_READ, '\u2007') self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'Z') self.assertRaises(ValueError, get_contiguous, nd, 255, 'A') # cmp_contig() nd = ndarray([1], shape=[1]) self.assertRaises(TypeError, cmp_contig, 1, 2, 3, 4, 5) self.assertRaises(TypeError, cmp_contig, {}, nd) self.assertRaises(TypeError, cmp_contig, nd, {}) # is_contiguous() nd = ndarray([1], shape=[1]) self.assertRaises(TypeError, is_contiguous, 1, 2, 3, 4, 5) self.assertRaises(TypeError, is_contiguous, {}, 'A') self.assertRaises(TypeError, is_contiguous, nd, 201) def test_ndarray_linked_list(self): for perm in permutations(range(5)): m = [0]*5 nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT) m[0] = memoryview(nd) for i in range(1, 5): nd.push([1,2,3], shape=[3]) m[i] = memoryview(nd) for i in range(5): m[perm[i]].release() self.assertRaises(BufferError, nd.pop) del nd def test_ndarray_format_scalar(self): # ndim = 0: scalar for fmt, scalar, _ in iter_format(0): itemsize = struct.calcsize(fmt) nd = ndarray(scalar, shape=(), format=fmt) self.verify(nd, obj=None, itemsize=itemsize, fmt=fmt, readonly=True, ndim=0, shape=(), strides=(), lst=scalar) def test_ndarray_format_shape(self): # ndim = 1, shape = [n] nitems = randrange(1, 10) for fmt, items, _ in iter_format(nitems): itemsize = struct.calcsize(fmt) for flags in (0, ND_PIL): nd = ndarray(items, shape=[nitems], format=fmt, flags=flags) self.verify(nd, obj=None, itemsize=itemsize, fmt=fmt, readonly=True, ndim=1, shape=(nitems,), strides=(itemsize,), lst=items) def test_ndarray_format_strides(self): # ndim = 1, strides nitems = randrange(1, 30) for fmt, items, _ in iter_format(nitems): itemsize = struct.calcsize(fmt) for step in range(-5, 5): if step == 0: continue shape = [len(items[::step])] strides = [step*itemsize] offset = itemsize*(nitems-1) if step < 0 else 0 for flags in (0, ND_PIL): nd = ndarray(items, shape=shape, strides=strides, format=fmt, offset=offset, flags=flags) self.verify(nd, obj=None, itemsize=itemsize, fmt=fmt, readonly=True, ndim=1, shape=shape, strides=strides, lst=items[::step]) def test_ndarray_fortran(self): items = [1,2,3,4,5,6,7,8,9,10,11,12] ex = ndarray(items, shape=(3, 4), strides=(1, 3)) nd = ndarray(ex, getbuf=PyBUF_F_CONTIGUOUS|PyBUF_FORMAT) self.assertEqual(nd.tolist(), farray(items, (3, 4))) def test_ndarray_multidim(self): for ndim in range(5): shape_t = [randrange(2, 10) for _ in range(ndim)] nitems = prod(shape_t) for shape in permutations(shape_t): fmt, items, _ = randitems(nitems) itemsize = struct.calcsize(fmt) for flags in (0, ND_PIL): if ndim == 0 and flags == ND_PIL: continue # C array nd = ndarray(items, shape=shape, format=fmt, flags=flags) strides = strides_from_shape(ndim, shape, itemsize, 'C') lst = carray(items, shape) self.verify(nd, obj=None, itemsize=itemsize, fmt=fmt, readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) if is_memoryview_format(fmt): # memoryview: reconstruct strides ex = ndarray(items, shape=shape, format=fmt) nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) self.assertTrue(nd.strides == ()) mv = nd.memoryview_from_buffer() self.verify(mv, obj=None, itemsize=itemsize, fmt=fmt, readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) # Fortran array nd = ndarray(items, shape=shape, format=fmt, flags=flags|ND_FORTRAN) strides = strides_from_shape(ndim, shape, itemsize, 'F') lst = farray(items, shape) self.verify(nd, obj=None, itemsize=itemsize, fmt=fmt, readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) def test_ndarray_index_invalid(self): # not writable nd = ndarray([1], shape=[1]) self.assertRaises(TypeError, nd.__setitem__, 1, 8) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertRaises(TypeError, mv.__setitem__, 1, 8) # cannot be deleted nd = ndarray([1], shape=[1], flags=ND_WRITABLE) self.assertRaises(TypeError, nd.__delitem__, 1) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertRaises(TypeError, mv.__delitem__, 1) # overflow nd = ndarray([1], shape=[1], flags=ND_WRITABLE) self.assertRaises(OverflowError, nd.__getitem__, 1<<64) self.assertRaises(OverflowError, nd.__setitem__, 1<<64, 8) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertRaises(IndexError, mv.__getitem__, 1<<64) self.assertRaises(IndexError, mv.__setitem__, 1<<64, 8) # format items = [1,2,3,4,5,6,7,8] nd = ndarray(items, shape=[len(items)], format="B", flags=ND_WRITABLE) self.assertRaises(struct.error, nd.__setitem__, 2, 300) self.assertRaises(ValueError, nd.__setitem__, 1, (100, 200)) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertRaises(ValueError, mv.__setitem__, 2, 300) self.assertRaises(TypeError, mv.__setitem__, 1, (100, 200)) items = [(1,2), (3,4), (5,6)] nd = ndarray(items, shape=[len(items)], format="LQ", flags=ND_WRITABLE) self.assertRaises(ValueError, nd.__setitem__, 2, 300) self.assertRaises(struct.error, nd.__setitem__, 1, (b'\x001', 200)) def test_ndarray_index_scalar(self): # scalar nd = ndarray(1, shape=(), flags=ND_WRITABLE) mv = memoryview(nd) self.assertEqual(mv, nd) x = nd[()]; self.assertEqual(x, 1) x = nd[...]; self.assertEqual(x.tolist(), nd.tolist()) x = mv[()]; self.assertEqual(x, 1) x = mv[...]; self.assertEqual(x.tolist(), nd.tolist()) self.assertRaises(TypeError, nd.__getitem__, 0) self.assertRaises(TypeError, mv.__getitem__, 0) self.assertRaises(TypeError, nd.__setitem__, 0, 8) self.assertRaises(TypeError, mv.__setitem__, 0, 8) self.assertEqual(nd.tolist(), 1) self.assertEqual(mv.tolist(), 1) nd[()] = 9; self.assertEqual(nd.tolist(), 9) mv[()] = 9; self.assertEqual(mv.tolist(), 9) nd[...] = 5; self.assertEqual(nd.tolist(), 5) mv[...] = 5; self.assertEqual(mv.tolist(), 5) def test_ndarray_index_null_strides(self): ex = ndarray(list(range(2*4)), shape=[2, 4], flags=ND_WRITABLE) nd = ndarray(ex, getbuf=PyBUF_CONTIG) # Sub-views are only possible for full exporters. self.assertRaises(BufferError, nd.__getitem__, 1) # Same for slices. self.assertRaises(BufferError, nd.__getitem__, slice(3,5,1)) def test_ndarray_index_getitem_single(self): # getitem for fmt, items, _ in iter_format(5): nd = ndarray(items, shape=[5], format=fmt) for i in range(-5, 5): self.assertEqual(nd[i], items[i]) self.assertRaises(IndexError, nd.__getitem__, -6) self.assertRaises(IndexError, nd.__getitem__, 5) if is_memoryview_format(fmt): mv = memoryview(nd) self.assertEqual(mv, nd) for i in range(-5, 5): self.assertEqual(mv[i], items[i]) self.assertRaises(IndexError, mv.__getitem__, -6) self.assertRaises(IndexError, mv.__getitem__, 5) # getitem with null strides for fmt, items, _ in iter_format(5): ex = ndarray(items, shape=[5], flags=ND_WRITABLE, format=fmt) nd = ndarray(ex, getbuf=PyBUF_CONTIG|PyBUF_FORMAT) for i in range(-5, 5): self.assertEqual(nd[i], items[i]) if is_memoryview_format(fmt): mv = nd.memoryview_from_buffer() self.assertIs(mv.__eq__(nd), NotImplemented) for i in range(-5, 5): self.assertEqual(mv[i], items[i]) # getitem with null format items = [1,2,3,4,5] ex = ndarray(items, shape=[5]) nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO) for i in range(-5, 5): self.assertEqual(nd[i], items[i]) # getitem with null shape/strides/format items = [1,2,3,4,5] ex = ndarray(items, shape=[5]) nd = ndarray(ex, getbuf=PyBUF_SIMPLE) for i in range(-5, 5): self.assertEqual(nd[i], items[i]) def test_ndarray_index_setitem_single(self): # assign single value for fmt, items, single_item in iter_format(5): nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) for i in range(5): items[i] = single_item nd[i] = single_item self.assertEqual(nd.tolist(), items) self.assertRaises(IndexError, nd.__setitem__, -6, single_item) self.assertRaises(IndexError, nd.__setitem__, 5, single_item) if not is_memoryview_format(fmt): continue nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) mv = memoryview(nd) self.assertEqual(mv, nd) for i in range(5): items[i] = single_item mv[i] = single_item self.assertEqual(mv.tolist(), items) self.assertRaises(IndexError, mv.__setitem__, -6, single_item) self.assertRaises(IndexError, mv.__setitem__, 5, single_item) # assign single value: lobject = robject for fmt, items, single_item in iter_format(5): nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) for i in range(-5, 4): items[i] = items[i+1] nd[i] = nd[i+1] self.assertEqual(nd.tolist(), items) if not is_memoryview_format(fmt): continue nd = ndarray(items, shape=[5], format=fmt, flags=ND_WRITABLE) mv = memoryview(nd) self.assertEqual(mv, nd) for i in range(-5, 4): items[i] = items[i+1] mv[i] = mv[i+1] self.assertEqual(mv.tolist(), items) def test_ndarray_index_getitem_multidim(self): shape_t = (2, 3, 5) nitems = prod(shape_t) for shape in permutations(shape_t): fmt, items, _ = randitems(nitems) for flags in (0, ND_PIL): # C array nd = ndarray(items, shape=shape, format=fmt, flags=flags) lst = carray(items, shape) for i in range(-shape[0], shape[0]): self.assertEqual(lst[i], nd[i].tolist()) for j in range(-shape[1], shape[1]): self.assertEqual(lst[i][j], nd[i][j].tolist()) for k in range(-shape[2], shape[2]): self.assertEqual(lst[i][j][k], nd[i][j][k]) # Fortran array nd = ndarray(items, shape=shape, format=fmt, flags=flags|ND_FORTRAN) lst = farray(items, shape) for i in range(-shape[0], shape[0]): self.assertEqual(lst[i], nd[i].tolist()) for j in range(-shape[1], shape[1]): self.assertEqual(lst[i][j], nd[i][j].tolist()) for k in range(shape[2], shape[2]): self.assertEqual(lst[i][j][k], nd[i][j][k]) def test_ndarray_sequence(self): nd = ndarray(1, shape=()) self.assertRaises(TypeError, eval, "1 in nd", locals()) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertRaises(TypeError, eval, "1 in mv", locals()) for fmt, items, _ in iter_format(5): nd = ndarray(items, shape=[5], format=fmt) for i, v in enumerate(nd): self.assertEqual(v, items[i]) self.assertTrue(v in nd) if is_memoryview_format(fmt): mv = memoryview(nd) for i, v in enumerate(mv): self.assertEqual(v, items[i]) self.assertTrue(v in mv) def test_ndarray_slice_invalid(self): items = [1,2,3,4,5,6,7,8] # rvalue is not an exporter xl = ndarray(items, shape=[8], flags=ND_WRITABLE) ml = memoryview(xl) self.assertRaises(TypeError, xl.__setitem__, slice(0,8,1), items) self.assertRaises(TypeError, ml.__setitem__, slice(0,8,1), items) # rvalue is not a full exporter xl = ndarray(items, shape=[8], flags=ND_WRITABLE) ex = ndarray(items, shape=[8], flags=ND_WRITABLE) xr = ndarray(ex, getbuf=PyBUF_ND) self.assertRaises(BufferError, xl.__setitem__, slice(0,8,1), xr) # zero step nd = ndarray(items, shape=[8], format="L", flags=ND_WRITABLE) mv = memoryview(nd) self.assertRaises(ValueError, nd.__getitem__, slice(0,1,0)) self.assertRaises(ValueError, mv.__getitem__, slice(0,1,0)) nd = ndarray(items, shape=[2,4], format="L", flags=ND_WRITABLE) mv = memoryview(nd) self.assertRaises(ValueError, nd.__getitem__, (slice(0,1,1), slice(0,1,0))) self.assertRaises(ValueError, nd.__getitem__, (slice(0,1,0), slice(0,1,1))) self.assertRaises(TypeError, nd.__getitem__, "@%$") self.assertRaises(TypeError, nd.__getitem__, ("@%$", slice(0,1,1))) self.assertRaises(TypeError, nd.__getitem__, (slice(0,1,1), {})) # memoryview: not implemented self.assertRaises(NotImplementedError, mv.__getitem__, (slice(0,1,1), slice(0,1,0))) self.assertRaises(TypeError, mv.__getitem__, "@%$") # differing format xl = ndarray(items, shape=[8], format="B", flags=ND_WRITABLE) xr = ndarray(items, shape=[8], format="b") ml = memoryview(xl) mr = memoryview(xr) self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) self.assertEqual(xl.tolist(), items) self.assertRaises(ValueError, ml.__setitem__, slice(0,1,1), mr[7:8]) self.assertEqual(ml.tolist(), items) # differing itemsize xl = ndarray(items, shape=[8], format="B", flags=ND_WRITABLE) yr = ndarray(items, shape=[8], format="L") ml = memoryview(xl) mr = memoryview(xr) self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) self.assertEqual(xl.tolist(), items) self.assertRaises(ValueError, ml.__setitem__, slice(0,1,1), mr[7:8]) self.assertEqual(ml.tolist(), items) # differing ndim xl = ndarray(items, shape=[2, 4], format="b", flags=ND_WRITABLE) xr = ndarray(items, shape=[8], format="b") ml = memoryview(xl) mr = memoryview(xr) self.assertRaises(ValueError, xl.__setitem__, slice(0,1,1), xr[7:8]) self.assertEqual(xl.tolist(), [[1,2,3,4], [5,6,7,8]]) self.assertRaises(NotImplementedError, ml.__setitem__, slice(0,1,1), mr[7:8]) # differing shape xl = ndarray(items, shape=[8], format="b", flags=ND_WRITABLE) xr = ndarray(items, shape=[8], format="b") ml = memoryview(xl) mr = memoryview(xr) self.assertRaises(ValueError, xl.__setitem__, slice(0,2,1), xr[7:8]) self.assertEqual(xl.tolist(), items) self.assertRaises(ValueError, ml.__setitem__, slice(0,2,1), mr[7:8]) self.assertEqual(ml.tolist(), items) # _testbuffer.c module functions self.assertRaises(TypeError, slice_indices, slice(0,1,2), {}) self.assertRaises(TypeError, slice_indices, "###########", 1) self.assertRaises(ValueError, slice_indices, slice(0,1,0), 4) x = ndarray(items, shape=[8], format="b", flags=ND_PIL) self.assertRaises(TypeError, x.add_suboffsets) ex = ndarray(items, shape=[8], format="B") x = ndarray(ex, getbuf=PyBUF_SIMPLE) self.assertRaises(TypeError, x.add_suboffsets) def test_ndarray_slice_zero_shape(self): items = [1,2,3,4,5,6,7,8,9,10,11,12] x = ndarray(items, shape=[12], format="L", flags=ND_WRITABLE) y = ndarray(items, shape=[12], format="L") x[4:4] = y[9:9] self.assertEqual(x.tolist(), items) ml = memoryview(x) mr = memoryview(y) self.assertEqual(ml, x) self.assertEqual(ml, y) ml[4:4] = mr[9:9] self.assertEqual(ml.tolist(), items) x = ndarray(items, shape=[3, 4], format="L", flags=ND_WRITABLE) y = ndarray(items, shape=[4, 3], format="L") x[1:2, 2:2] = y[1:2, 3:3] self.assertEqual(x.tolist(), carray(items, [3, 4])) def test_ndarray_slice_multidim(self): shape_t = (2, 3, 5) ndim = len(shape_t) nitems = prod(shape_t) for shape in permutations(shape_t): fmt, items, _ = randitems(nitems) itemsize = struct.calcsize(fmt) for flags in (0, ND_PIL): nd = ndarray(items, shape=shape, format=fmt, flags=flags) lst = carray(items, shape) for slices in rslices_ndim(ndim, shape): listerr = None try: sliced = multislice(lst, slices) except Exception as e: listerr = e.__class__ nderr = None try: ndsliced = nd[slices] except Exception as e: nderr = e.__class__ if nderr or listerr: self.assertIs(nderr, listerr) else: self.assertEqual(ndsliced.tolist(), sliced) def test_ndarray_slice_redundant_suboffsets(self): shape_t = (2, 3, 5, 2) ndim = len(shape_t) nitems = prod(shape_t) for shape in permutations(shape_t): fmt, items, _ = randitems(nitems) itemsize = struct.calcsize(fmt) nd = ndarray(items, shape=shape, format=fmt) nd.add_suboffsets() ex = ndarray(items, shape=shape, format=fmt) ex.add_suboffsets() mv = memoryview(ex) lst = carray(items, shape) for slices in rslices_ndim(ndim, shape): listerr = None try: sliced = multislice(lst, slices) except Exception as e: listerr = e.__class__ nderr = None try: ndsliced = nd[slices] except Exception as e: nderr = e.__class__ if nderr or listerr: self.assertIs(nderr, listerr) else: self.assertEqual(ndsliced.tolist(), sliced) def test_ndarray_slice_assign_single(self): for fmt, items, _ in iter_format(5): for lslice in genslices(5): for rslice in genslices(5): for flags in (0, ND_PIL): f = flags|ND_WRITABLE nd = ndarray(items, shape=[5], format=fmt, flags=f) ex = ndarray(items, shape=[5], format=fmt, flags=f) mv = memoryview(ex) lsterr = None diff_structure = None lst = items[:] try: lval = lst[lslice] rval = lst[rslice] lst[lslice] = lst[rslice] diff_structure = len(lval) != len(rval) except Exception as e: lsterr = e.__class__ nderr = None try: nd[lslice] = nd[rslice] except Exception as e: nderr = e.__class__ if diff_structure: # ndarray cannot change shape self.assertIs(nderr, ValueError) else: self.assertEqual(nd.tolist(), lst) self.assertIs(nderr, lsterr) if not is_memoryview_format(fmt): continue mverr = None try: mv[lslice] = mv[rslice] except Exception as e: mverr = e.__class__ if diff_structure: # memoryview cannot change shape self.assertIs(mverr, ValueError) else: self.assertEqual(mv.tolist(), lst) self.assertEqual(mv, nd) self.assertIs(mverr, lsterr) self.verify(mv, obj=ex, itemsize=nd.itemsize, fmt=fmt, readonly=False, ndim=nd.ndim, shape=nd.shape, strides=nd.strides, lst=nd.tolist()) def test_ndarray_slice_assign_multidim(self): shape_t = (2, 3, 5) ndim = len(shape_t) nitems = prod(shape_t) for shape in permutations(shape_t): fmt, items, _ = randitems(nitems) for flags in (0, ND_PIL): for _ in range(ITERATIONS): lslices, rslices = randslice_from_shape(ndim, shape) nd = ndarray(items, shape=shape, format=fmt, flags=flags|ND_WRITABLE) lst = carray(items, shape) listerr = None try: result = multislice_assign(lst, lst, lslices, rslices) except Exception as e: listerr = e.__class__ nderr = None try: nd[lslices] = nd[rslices] except Exception as e: nderr = e.__class__ if nderr or listerr: self.assertIs(nderr, listerr) else: self.assertEqual(nd.tolist(), result) def test_ndarray_random(self): # construction of valid arrays for _ in range(ITERATIONS): for fmt in fmtdict['@']: itemsize = struct.calcsize(fmt) t = rand_structure(itemsize, True, maxdim=MAXDIM, maxshape=MAXSHAPE) self.assertTrue(verify_structure(*t)) items = randitems_from_structure(fmt, t) x = ndarray_from_structure(items, fmt, t) xlist = x.tolist() mv = memoryview(x) if is_memoryview_format(fmt): mvlist = mv.tolist() self.assertEqual(mvlist, xlist) if t[2] > 0: # ndim > 0: test against suboffsets representation. y = ndarray_from_structure(items, fmt, t, flags=ND_PIL) ylist = y.tolist() self.assertEqual(xlist, ylist) mv = memoryview(y) if is_memoryview_format(fmt): self.assertEqual(mv, y) mvlist = mv.tolist() self.assertEqual(mvlist, ylist) if numpy_array: shape = t[3] if 0 in shape: continue # https://github.com/numpy/numpy/issues/2503 z = numpy_array_from_structure(items, fmt, t) self.verify(x, obj=None, itemsize=z.itemsize, fmt=fmt, readonly=False, ndim=z.ndim, shape=z.shape, strides=z.strides, lst=z.tolist()) def test_ndarray_random_invalid(self): # exceptions during construction of invalid arrays for _ in range(ITERATIONS): for fmt in fmtdict['@']: itemsize = struct.calcsize(fmt) t = rand_structure(itemsize, False, maxdim=MAXDIM, maxshape=MAXSHAPE) self.assertFalse(verify_structure(*t)) items = randitems_from_structure(fmt, t) nderr = False try: x = ndarray_from_structure(items, fmt, t) except Exception as e: nderr = e.__class__ self.assertTrue(nderr) if numpy_array: numpy_err = False try: y = numpy_array_from_structure(items, fmt, t) except Exception as e: numpy_err = e.__class__ if 0: # https://github.com/numpy/numpy/issues/2503 self.assertTrue(numpy_err) def test_ndarray_random_slice_assign(self): # valid slice assignments for _ in range(ITERATIONS): for fmt in fmtdict['@']: itemsize = struct.calcsize(fmt) lshape, rshape, lslices, rslices = \ rand_aligned_slices(maxdim=MAXDIM, maxshape=MAXSHAPE) tl = rand_structure(itemsize, True, shape=lshape) tr = rand_structure(itemsize, True, shape=rshape) self.assertTrue(verify_structure(*tl)) self.assertTrue(verify_structure(*tr)) litems = randitems_from_structure(fmt, tl) ritems = randitems_from_structure(fmt, tr) xl = ndarray_from_structure(litems, fmt, tl) xr = ndarray_from_structure(ritems, fmt, tr) xl[lslices] = xr[rslices] xllist = xl.tolist() xrlist = xr.tolist() ml = memoryview(xl) mr = memoryview(xr) self.assertEqual(ml.tolist(), xllist) self.assertEqual(mr.tolist(), xrlist) if tl[2] > 0 and tr[2] > 0: # ndim > 0: test against suboffsets representation. yl = ndarray_from_structure(litems, fmt, tl, flags=ND_PIL) yr = ndarray_from_structure(ritems, fmt, tr, flags=ND_PIL) yl[lslices] = yr[rslices] yllist = yl.tolist() yrlist = yr.tolist() self.assertEqual(xllist, yllist) self.assertEqual(xrlist, yrlist) ml = memoryview(yl) mr = memoryview(yr) self.assertEqual(ml.tolist(), yllist) self.assertEqual(mr.tolist(), yrlist) if numpy_array: if 0 in lshape or 0 in rshape: continue # https://github.com/numpy/numpy/issues/2503 zl = numpy_array_from_structure(litems, fmt, tl) zr = numpy_array_from_structure(ritems, fmt, tr) zl[lslices] = zr[rslices] if not is_overlapping(tl) and not is_overlapping(tr): # Slice assignment of overlapping structures # is undefined in NumPy. self.verify(xl, obj=None, itemsize=zl.itemsize, fmt=fmt, readonly=False, ndim=zl.ndim, shape=zl.shape, strides=zl.strides, lst=zl.tolist()) self.verify(xr, obj=None, itemsize=zr.itemsize, fmt=fmt, readonly=False, ndim=zr.ndim, shape=zr.shape, strides=zr.strides, lst=zr.tolist()) def test_ndarray_re_export(self): items = [1,2,3,4,5,6,7,8,9,10,11,12] nd = ndarray(items, shape=[3,4], flags=ND_PIL) ex = ndarray(nd) self.assertTrue(ex.flags & ND_PIL) self.assertIs(ex.obj, nd) self.assertEqual(ex.suboffsets, (0, -1)) self.assertFalse(ex.c_contiguous) self.assertFalse(ex.f_contiguous) self.assertFalse(ex.contiguous) def test_ndarray_zero_shape(self): # zeros in shape for flags in (0, ND_PIL): nd = ndarray([1,2,3], shape=[0], flags=flags) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertEqual(nd.tolist(), []) self.assertEqual(mv.tolist(), []) nd = ndarray([1,2,3], shape=[0,3,3], flags=flags) self.assertEqual(nd.tolist(), []) nd = ndarray([1,2,3], shape=[3,0,3], flags=flags) self.assertEqual(nd.tolist(), [[], [], []]) nd = ndarray([1,2,3], shape=[3,3,0], flags=flags) self.assertEqual(nd.tolist(), [[[], [], []], [[], [], []], [[], [], []]]) def test_ndarray_zero_strides(self): # zero strides for flags in (0, ND_PIL): nd = ndarray([1], shape=[5], strides=[0], flags=flags) mv = memoryview(nd) self.assertEqual(mv, nd) self.assertEqual(nd.tolist(), [1, 1, 1, 1, 1]) self.assertEqual(mv.tolist(), [1, 1, 1, 1, 1]) def test_ndarray_offset(self): nd = ndarray(list(range(20)), shape=[3], offset=7) self.assertEqual(nd.offset, 7) self.assertEqual(nd.tolist(), [7,8,9]) def test_ndarray_memoryview_from_buffer(self): for flags in (0, ND_PIL): nd = ndarray(list(range(3)), shape=[3], flags=flags) m = nd.memoryview_from_buffer() self.assertEqual(m, nd) def test_ndarray_get_pointer(self): for flags in (0, ND_PIL): nd = ndarray(list(range(3)), shape=[3], flags=flags) for i in range(3): self.assertEqual(nd[i], get_pointer(nd, [i])) def test_ndarray_tolist_null_strides(self): ex = ndarray(list(range(20)), shape=[2,2,5]) nd = ndarray(ex, getbuf=PyBUF_ND|PyBUF_FORMAT) self.assertEqual(nd.tolist(), ex.tolist()) m = memoryview(ex) self.assertEqual(m.tolist(), ex.tolist()) def test_ndarray_cmp_contig(self): self.assertFalse(cmp_contig(b"123", b"456")) x = ndarray(list(range(12)), shape=[3,4]) y = ndarray(list(range(12)), shape=[4,3]) self.assertFalse(cmp_contig(x, y)) x = ndarray([1], shape=[1], format="B") self.assertTrue(cmp_contig(x, b'\x01')) self.assertTrue(cmp_contig(b'\x01', x)) def test_ndarray_hash(self): a = array.array('L', [1,2,3]) nd = ndarray(a) self.assertRaises(ValueError, hash, nd) # one-dimensional b = bytes(list(range(12))) nd = ndarray(list(range(12)), shape=[12]) self.assertEqual(hash(nd), hash(b)) # C-contiguous nd = ndarray(list(range(12)), shape=[3,4]) self.assertEqual(hash(nd), hash(b)) nd = ndarray(list(range(12)), shape=[3,2,2]) self.assertEqual(hash(nd), hash(b)) # Fortran contiguous b = bytes(transpose(list(range(12)), shape=[4,3])) nd = ndarray(list(range(12)), shape=[3,4], flags=ND_FORTRAN) self.assertEqual(hash(nd), hash(b)) b = bytes(transpose(list(range(12)), shape=[2,3,2])) nd = ndarray(list(range(12)), shape=[2,3,2], flags=ND_FORTRAN) self.assertEqual(hash(nd), hash(b)) # suboffsets b = bytes(list(range(12))) nd = ndarray(list(range(12)), shape=[2,2,3], flags=ND_PIL) self.assertEqual(hash(nd), hash(b)) # non-byte formats nd = ndarray(list(range(12)), shape=[2,2,3], format='L') self.assertEqual(hash(nd), hash(nd.tobytes())) def test_py_buffer_to_contiguous(self): # The requests are used in _testbuffer.c:py_buffer_to_contiguous # to generate buffers without full information for testing. requests = ( # distinct flags PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND, PyBUF_SIMPLE, # compound requests PyBUF_FULL, PyBUF_FULL_RO, PyBUF_RECORDS, PyBUF_RECORDS_RO, PyBUF_STRIDED, PyBUF_STRIDED_RO, PyBUF_CONTIG, PyBUF_CONTIG_RO, ) # no buffer interface self.assertRaises(TypeError, py_buffer_to_contiguous, {}, 'F', PyBUF_FULL_RO) # scalar, read-only request nd = ndarray(9, shape=(), format="L", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: for request in requests: b = py_buffer_to_contiguous(nd, order, request) self.assertEqual(b, nd.tobytes()) # zeros in shape nd = ndarray([1], shape=[0], format="L", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: for request in requests: b = py_buffer_to_contiguous(nd, order, request) self.assertEqual(b, b'') nd = ndarray(list(range(8)), shape=[2, 0, 7], format="L", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: for request in requests: b = py_buffer_to_contiguous(nd, order, request) self.assertEqual(b, b'') ### One-dimensional arrays are trivial, since Fortran and C order ### are the same. # one-dimensional for f in [0, ND_FORTRAN]: nd = ndarray([1], shape=[1], format="h", flags=f|ND_WRITABLE) ndbytes = nd.tobytes() for order in ['C', 'F', 'A']: for request in requests: b = py_buffer_to_contiguous(nd, order, request) self.assertEqual(b, ndbytes) nd = ndarray([1, 2, 3], shape=[3], format="b", flags=f|ND_WRITABLE) ndbytes = nd.tobytes() for order in ['C', 'F', 'A']: for request in requests: b = py_buffer_to_contiguous(nd, order, request) self.assertEqual(b, ndbytes) # one-dimensional, non-contiguous input nd = ndarray([1, 2, 3], shape=[2], strides=[2], flags=ND_WRITABLE) ndbytes = nd.tobytes() for order in ['C', 'F', 'A']: for request in [PyBUF_STRIDES, PyBUF_FULL]: b = py_buffer_to_contiguous(nd, order, request) self.assertEqual(b, ndbytes) nd = nd[::-1] ndbytes = nd.tobytes() for order in ['C', 'F', 'A']: for request in requests: try: b = py_buffer_to_contiguous(nd, order, request) except BufferError: continue self.assertEqual(b, ndbytes) ### ### Multi-dimensional arrays: ### ### The goal here is to preserve the logical representation of the ### input array but change the physical representation if necessary. ### ### _testbuffer example: ### ==================== ### ### C input array: ### -------------- ### >>> nd = ndarray(list(range(12)), shape=[3, 4]) ### >>> nd.tolist() ### [[0, 1, 2, 3], ### [4, 5, 6, 7], ### [8, 9, 10, 11]] ### ### Fortran output: ### --------------- ### >>> py_buffer_to_contiguous(nd, 'F', PyBUF_FULL_RO) ### >>> b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b' ### ### The return value corresponds to this input list for ### _testbuffer's ndarray: ### >>> nd = ndarray([0,4,8,1,5,9,2,6,10,3,7,11], shape=[3,4], ### flags=ND_FORTRAN) ### >>> nd.tolist() ### [[0, 1, 2, 3], ### [4, 5, 6, 7], ### [8, 9, 10, 11]] ### ### The logical array is the same, but the values in memory are now ### in Fortran order. ### ### NumPy example: ### ============== ### _testbuffer's ndarray takes lists to initialize the memory. ### Here's the same sequence in NumPy: ### ### C input: ### -------- ### >>> nd = ndarray(buffer=bytearray(list(range(12))), ### shape=[3, 4], dtype='B') ### >>> nd ### array([[ 0, 1, 2, 3], ### [ 4, 5, 6, 7], ### [ 8, 9, 10, 11]], dtype=uint8) ### ### Fortran output: ### --------------- ### >>> fortran_buf = nd.tobytes(order='F') ### >>> fortran_buf ### b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b' ### ### >>> nd = ndarray(buffer=fortran_buf, shape=[3, 4], ### dtype='B', order='F') ### ### >>> nd ### array([[ 0, 1, 2, 3], ### [ 4, 5, 6, 7], ### [ 8, 9, 10, 11]], dtype=uint8) ### # multi-dimensional, contiguous input lst = list(range(12)) for f in [0, ND_FORTRAN]: nd = ndarray(lst, shape=[3, 4], flags=f|ND_WRITABLE) if numpy_array: na = numpy_array(buffer=bytearray(lst), shape=[3, 4], dtype='B', order='C' if f == 0 else 'F') # 'C' request if f == ND_FORTRAN: # 'F' to 'C' x = ndarray(transpose(lst, [4, 3]), shape=[3, 4], flags=ND_WRITABLE) expected = x.tobytes() else: expected = nd.tobytes() for request in requests: try: b = py_buffer_to_contiguous(nd, 'C', request) except BufferError: continue self.assertEqual(b, expected) # Check that output can be used as the basis for constructing # a C array that is logically identical to the input array. y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) self.assertEqual(memoryview(y), memoryview(nd)) if numpy_array: self.assertEqual(b, na.tobytes(order='C')) # 'F' request if f == 0: # 'C' to 'F' x = ndarray(transpose(lst, [3, 4]), shape=[4, 3], flags=ND_WRITABLE) else: x = ndarray(lst, shape=[3, 4], flags=ND_WRITABLE) expected = x.tobytes() for request in [PyBUF_FULL, PyBUF_FULL_RO, PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND]: try: b = py_buffer_to_contiguous(nd, 'F', request) except BufferError: continue self.assertEqual(b, expected) # Check that output can be used as the basis for constructing # a Fortran array that is logically identical to the input array. y = ndarray([v for v in b], shape=[3, 4], flags=ND_FORTRAN|ND_WRITABLE) self.assertEqual(memoryview(y), memoryview(nd)) if numpy_array: self.assertEqual(b, na.tobytes(order='F')) # 'A' request if f == ND_FORTRAN: x = ndarray(lst, shape=[3, 4], flags=ND_WRITABLE) expected = x.tobytes() else: expected = nd.tobytes() for request in [PyBUF_FULL, PyBUF_FULL_RO, PyBUF_INDIRECT, PyBUF_STRIDES, PyBUF_ND]: try: b = py_buffer_to_contiguous(nd, 'A', request) except BufferError: continue self.assertEqual(b, expected) # Check that output can be used as the basis for constructing # an array with order=f that is logically identical to the input # array. y = ndarray([v for v in b], shape=[3, 4], flags=f|ND_WRITABLE) self.assertEqual(memoryview(y), memoryview(nd)) if numpy_array: self.assertEqual(b, na.tobytes(order='A')) # multi-dimensional, non-contiguous input nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL) # 'C' b = py_buffer_to_contiguous(nd, 'C', PyBUF_FULL_RO) self.assertEqual(b, nd.tobytes()) y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) self.assertEqual(memoryview(y), memoryview(nd)) # 'F' b = py_buffer_to_contiguous(nd, 'F', PyBUF_FULL_RO) x = ndarray(transpose(lst, [3, 4]), shape=[4, 3], flags=ND_WRITABLE) self.assertEqual(b, x.tobytes()) y = ndarray([v for v in b], shape=[3, 4], flags=ND_FORTRAN|ND_WRITABLE) self.assertEqual(memoryview(y), memoryview(nd)) # 'A' b = py_buffer_to_contiguous(nd, 'A', PyBUF_FULL_RO) self.assertEqual(b, nd.tobytes()) y = ndarray([v for v in b], shape=[3, 4], flags=ND_WRITABLE) self.assertEqual(memoryview(y), memoryview(nd)) def test_memoryview_construction(self): items_shape = [(9, []), ([1,2,3], [3]), (list(range(2*3*5)), [2,3,5])] # NumPy style, C-contiguous: for items, shape in items_shape: # From PEP-3118 compliant exporter: ex = ndarray(items, shape=shape) m = memoryview(ex) self.assertTrue(m.c_contiguous) self.assertTrue(m.contiguous) ndim = len(shape) strides = strides_from_shape(ndim, shape, 1, 'C') lst = carray(items, shape) self.verify(m, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) # From memoryview: m2 = memoryview(m) self.verify(m2, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) # PyMemoryView_FromBuffer(): no strides nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) self.assertEqual(nd.strides, ()) m = nd.memoryview_from_buffer() self.verify(m, obj=None, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) # PyMemoryView_FromBuffer(): no format, shape, strides nd = ndarray(ex, getbuf=PyBUF_SIMPLE) self.assertEqual(nd.format, '') self.assertEqual(nd.shape, ()) self.assertEqual(nd.strides, ()) m = nd.memoryview_from_buffer() lst = [items] if ndim == 0 else items self.verify(m, obj=None, itemsize=1, fmt='B', readonly=True, ndim=1, shape=[ex.nbytes], strides=(1,), lst=lst) # NumPy style, Fortran contiguous: for items, shape in items_shape: # From PEP-3118 compliant exporter: ex = ndarray(items, shape=shape, flags=ND_FORTRAN) m = memoryview(ex) self.assertTrue(m.f_contiguous) self.assertTrue(m.contiguous) ndim = len(shape) strides = strides_from_shape(ndim, shape, 1, 'F') lst = farray(items, shape) self.verify(m, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) # From memoryview: m2 = memoryview(m) self.verify(m2, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst) # PIL style: for items, shape in items_shape[1:]: # From PEP-3118 compliant exporter: ex = ndarray(items, shape=shape, flags=ND_PIL) m = memoryview(ex) ndim = len(shape) lst = carray(items, shape) self.verify(m, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=ex.strides, lst=lst) # From memoryview: m2 = memoryview(m) self.verify(m2, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=ndim, shape=shape, strides=ex.strides, lst=lst) # Invalid number of arguments: self.assertRaises(TypeError, memoryview, b'9', 'x') # Not a buffer provider: self.assertRaises(TypeError, memoryview, {}) # Non-compliant buffer provider: ex = ndarray([1,2,3], shape=[3]) nd = ndarray(ex, getbuf=PyBUF_SIMPLE) self.assertRaises(BufferError, memoryview, nd) nd = ndarray(ex, getbuf=PyBUF_CONTIG_RO|PyBUF_FORMAT) self.assertRaises(BufferError, memoryview, nd) # ndim > 64 nd = ndarray([1]*128, shape=[1]*128, format='L') self.assertRaises(ValueError, memoryview, nd) self.assertRaises(ValueError, nd.memoryview_from_buffer) self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'C') self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'F') self.assertRaises(ValueError, get_contiguous, nd[::-1], PyBUF_READ, 'C') def test_memoryview_cast_zero_shape(self): # Casts are undefined if buffer is multidimensional and shape # contains zeros. These arrays are regarded as C-contiguous by # Numpy and PyBuffer_GetContiguous(), so they are not caught by # the test for C-contiguity in memory_cast(). items = [1,2,3] for shape in ([0,3,3], [3,0,3], [0,3,3]): ex = ndarray(items, shape=shape) self.assertTrue(ex.c_contiguous) msrc = memoryview(ex) self.assertRaises(TypeError, msrc.cast, 'c') # Monodimensional empty view can be cast (issue #19014). for fmt, _, _ in iter_format(1, 'memoryview'): msrc = memoryview(b'') m = msrc.cast(fmt) self.assertEqual(m.tobytes(), b'') self.assertEqual(m.tolist(), []) check_sizeof = support.check_sizeof def test_memoryview_sizeof(self): check = self.check_sizeof vsize = support.calcvobjsize base_struct = 'Pnin 2P2n2i5P P' per_dim = '3n' items = list(range(8)) check(memoryview(b''), vsize(base_struct + 1 * per_dim)) a = ndarray(items, shape=[2, 4], format="b") check(memoryview(a), vsize(base_struct + 2 * per_dim)) a = ndarray(items, shape=[2, 2, 2], format="b") check(memoryview(a), vsize(base_struct + 3 * per_dim)) def test_memoryview_struct_module(self): class INT(object): def __init__(self, val): self.val = val def __int__(self): return self.val class IDX(object): def __init__(self, val): self.val = val def __index__(self): return self.val def f(): return 7 values = [INT(9), IDX(9), 2.2+3j, Decimal("-21.1"), 12.2, Fraction(5, 2), [1,2,3], {4,5,6}, {7:8}, (), (9,), True, False, None, Ellipsis, b'a', b'abc', bytearray(b'a'), bytearray(b'abc'), 'a', 'abc', r'a', r'abc', f, lambda x: x] for fmt, items, item in iter_format(10, 'memoryview'): ex = ndarray(items, shape=[10], format=fmt, flags=ND_WRITABLE) nd = ndarray(items, shape=[10], format=fmt, flags=ND_WRITABLE) m = memoryview(ex) struct.pack_into(fmt, nd, 0, item) m[0] = item self.assertEqual(m[0], nd[0]) itemsize = struct.calcsize(fmt) if 'P' in fmt: continue for v in values: struct_err = None try: struct.pack_into(fmt, nd, itemsize, v) except struct.error: struct_err = struct.error mv_err = None try: m[1] = v except (TypeError, ValueError) as e: mv_err = e.__class__ if struct_err or mv_err: self.assertIsNot(struct_err, None) self.assertIsNot(mv_err, None) else: self.assertEqual(m[1], nd[1]) def test_memoryview_cast_zero_strides(self): # Casts are undefined if strides contains zeros. These arrays are # (sometimes!) regarded as C-contiguous by Numpy, but not by # PyBuffer_GetContiguous(). ex = ndarray([1,2,3], shape=[3], strides=[0]) self.assertFalse(ex.c_contiguous) msrc = memoryview(ex) self.assertRaises(TypeError, msrc.cast, 'c') def test_memoryview_cast_invalid(self): # invalid format for sfmt in NON_BYTE_FORMAT: sformat = '@' + sfmt if randrange(2) else sfmt ssize = struct.calcsize(sformat) for dfmt in NON_BYTE_FORMAT: dformat = '@' + dfmt if randrange(2) else dfmt dsize = struct.calcsize(dformat) ex = ndarray(list(range(32)), shape=[32//ssize], format=sformat) msrc = memoryview(ex) self.assertRaises(TypeError, msrc.cast, dfmt, [32//dsize]) for sfmt, sitems, _ in iter_format(1): ex = ndarray(sitems, shape=[1], format=sfmt) msrc = memoryview(ex) for dfmt, _, _ in iter_format(1): if not is_memoryview_format(dfmt): self.assertRaises(ValueError, msrc.cast, dfmt, [32//dsize]) else: if not is_byte_format(sfmt) and not is_byte_format(dfmt): self.assertRaises(TypeError, msrc.cast, dfmt, [32//dsize]) # invalid shape size_h = struct.calcsize('h') size_d = struct.calcsize('d') ex = ndarray(list(range(2*2*size_d)), shape=[2,2,size_d], format='h') msrc = memoryview(ex) self.assertRaises(TypeError, msrc.cast, shape=[2,2,size_h], format='d') ex = ndarray(list(range(120)), shape=[1,2,3,4,5]) m = memoryview(ex) # incorrect number of args self.assertRaises(TypeError, m.cast) self.assertRaises(TypeError, m.cast, 1, 2, 3) # incorrect dest format type self.assertRaises(TypeError, m.cast, {}) # incorrect dest format self.assertRaises(ValueError, m.cast, "X") self.assertRaises(ValueError, m.cast, "@X") self.assertRaises(ValueError, m.cast, "@XY") # dest format not implemented self.assertRaises(ValueError, m.cast, "=B") self.assertRaises(ValueError, m.cast, "!L") self.assertRaises(ValueError, m.cast, "l") self.assertRaises(ValueError, m.cast, "BI") self.assertRaises(ValueError, m.cast, "xBI") # src format not implemented ex = ndarray([(1,2), (3,4)], shape=[2], format="II") m = memoryview(ex) self.assertRaises(NotImplementedError, m.__getitem__, 0) self.assertRaises(NotImplementedError, m.__setitem__, 0, 8) self.assertRaises(NotImplementedError, m.tolist) # incorrect shape type ex = ndarray(list(range(120)), shape=[1,2,3,4,5]) m = memoryview(ex) self.assertRaises(TypeError, m.cast, "B", shape={}) # incorrect shape elements ex = ndarray(list(range(120)), shape=[2*3*4*5]) m = memoryview(ex) self.assertRaises(OverflowError, m.cast, "B", shape=[2**64]) self.assertRaises(ValueError, m.cast, "B", shape=[-1]) self.assertRaises(ValueError, m.cast, "B", shape=[2,3,4,5,6,7,-1]) self.assertRaises(ValueError, m.cast, "B", shape=[2,3,4,5,6,7,0]) self.assertRaises(TypeError, m.cast, "B", shape=[2,3,4,5,6,7,'x']) # N-D -> N-D cast ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3,5,7,11]) m = memoryview(ex) self.assertRaises(TypeError, m.cast, "I", shape=[2,3,4,5]) # cast with ndim > 64 nd = ndarray(list(range(128)), shape=[128], format='I') m = memoryview(nd) self.assertRaises(ValueError, m.cast, 'I', [1]*128) # view->len not a multiple of itemsize ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3*5*7*11]) m = memoryview(ex) self.assertRaises(TypeError, m.cast, "I", shape=[2,3,4,5]) # product(shape) * itemsize != buffer size ex = ndarray(list([9 for _ in range(3*5*7*11)]), shape=[3*5*7*11]) m = memoryview(ex) self.assertRaises(TypeError, m.cast, "B", shape=[2,3,4,5]) # product(shape) * itemsize overflow nd = ndarray(list(range(128)), shape=[128], format='I') m1 = memoryview(nd) nd = ndarray(list(range(128)), shape=[128], format='B') m2 = memoryview(nd) if sys.maxsize == 2**63-1: self.assertRaises(TypeError, m1.cast, 'B', [7, 7, 73, 127, 337, 92737, 649657]) self.assertRaises(ValueError, m1.cast, 'B', [2**20, 2**20, 2**10, 2**10, 2**3]) self.assertRaises(ValueError, m2.cast, 'I', [2**20, 2**20, 2**10, 2**10, 2**1]) else: self.assertRaises(TypeError, m1.cast, 'B', [1, 2147483647]) self.assertRaises(ValueError, m1.cast, 'B', [2**10, 2**10, 2**5, 2**5, 2**1]) self.assertRaises(ValueError, m2.cast, 'I', [2**10, 2**10, 2**5, 2**3, 2**1]) def test_memoryview_cast(self): bytespec = ( ('B', lambda ex: list(ex.tobytes())), ('b', lambda ex: [x-256 if x > 127 else x for x in list(ex.tobytes())]), ('c', lambda ex: [bytes(chr(x), 'latin-1') for x in list(ex.tobytes())]), ) def iter_roundtrip(ex, m, items, fmt): srcsize = struct.calcsize(fmt) for bytefmt, to_bytelist in bytespec: m2 = m.cast(bytefmt) lst = to_bytelist(ex) self.verify(m2, obj=ex, itemsize=1, fmt=bytefmt, readonly=False, ndim=1, shape=[31*srcsize], strides=(1,), lst=lst, cast=True) m3 = m2.cast(fmt) self.assertEqual(m3, ex) lst = ex.tolist() self.verify(m3, obj=ex, itemsize=srcsize, fmt=fmt, readonly=False, ndim=1, shape=[31], strides=(srcsize,), lst=lst, cast=True) # cast from ndim = 0 to ndim = 1 srcsize = struct.calcsize('I') ex = ndarray(9, shape=[], format='I') destitems, destshape = cast_items(ex, 'B', 1) m = memoryview(ex) m2 = m.cast('B') self.verify(m2, obj=ex, itemsize=1, fmt='B', readonly=True, ndim=1, shape=destshape, strides=(1,), lst=destitems, cast=True) # cast from ndim = 1 to ndim = 0 destsize = struct.calcsize('I') ex = ndarray([9]*destsize, shape=[destsize], format='B') destitems, destshape = cast_items(ex, 'I', destsize, shape=[]) m = memoryview(ex) m2 = m.cast('I', shape=[]) self.verify(m2, obj=ex, itemsize=destsize, fmt='I', readonly=True, ndim=0, shape=(), strides=(), lst=destitems, cast=True) # array.array: roundtrip to/from bytes for fmt, items, _ in iter_format(31, 'array'): ex = array.array(fmt, items) m = memoryview(ex) iter_roundtrip(ex, m, items, fmt) # ndarray: roundtrip to/from bytes for fmt, items, _ in iter_format(31, 'memoryview'): ex = ndarray(items, shape=[31], format=fmt, flags=ND_WRITABLE) m = memoryview(ex) iter_roundtrip(ex, m, items, fmt) @support.requires_resource('cpu') def test_memoryview_cast_1D_ND(self): # Cast between C-contiguous buffers. At least one buffer must # be 1D, at least one format must be 'c', 'b' or 'B'. for _tshape in gencastshapes(): for char in fmtdict['@']: # Casts to _Bool are undefined if the source contains values # other than 0 or 1. if char == "?": continue tfmt = ('', '@')[randrange(2)] + char tsize = struct.calcsize(tfmt) n = prod(_tshape) * tsize obj = 'memoryview' if is_byte_format(tfmt) else 'bytefmt' for fmt, items, _ in iter_format(n, obj): size = struct.calcsize(fmt) shape = [n] if n > 0 else [] tshape = _tshape + [size] ex = ndarray(items, shape=shape, format=fmt) m = memoryview(ex) titems, tshape = cast_items(ex, tfmt, tsize, shape=tshape) if titems is None: self.assertRaises(TypeError, m.cast, tfmt, tshape) continue if titems == 'nan': continue # NaNs in lists are a recipe for trouble. # 1D -> ND nd = ndarray(titems, shape=tshape, format=tfmt) m2 = m.cast(tfmt, shape=tshape) ndim = len(tshape) strides = nd.strides lst = nd.tolist() self.verify(m2, obj=ex, itemsize=tsize, fmt=tfmt, readonly=True, ndim=ndim, shape=tshape, strides=strides, lst=lst, cast=True) # ND -> 1D m3 = m2.cast(fmt) m4 = m2.cast(fmt, shape=shape) ndim = len(shape) strides = ex.strides lst = ex.tolist() self.verify(m3, obj=ex, itemsize=size, fmt=fmt, readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst, cast=True) self.verify(m4, obj=ex, itemsize=size, fmt=fmt, readonly=True, ndim=ndim, shape=shape, strides=strides, lst=lst, cast=True) if ctypes: # format: "T{>l:x:>d:y:}" class BEPoint(ctypes.BigEndianStructure): _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] point = BEPoint(100, 200.1) m1 = memoryview(point) m2 = m1.cast('B') self.assertEqual(m2.obj, point) self.assertEqual(m2.itemsize, 1) self.assertIs(m2.readonly, False) self.assertEqual(m2.ndim, 1) self.assertEqual(m2.shape, (m2.nbytes,)) self.assertEqual(m2.strides, (1,)) self.assertEqual(m2.suboffsets, ()) x = ctypes.c_double(1.2) m1 = memoryview(x) m2 = m1.cast('c') self.assertEqual(m2.obj, x) self.assertEqual(m2.itemsize, 1) self.assertIs(m2.readonly, False) self.assertEqual(m2.ndim, 1) self.assertEqual(m2.shape, (m2.nbytes,)) self.assertEqual(m2.strides, (1,)) self.assertEqual(m2.suboffsets, ()) def test_memoryview_tolist(self): # Most tolist() tests are in self.verify() etc. a = array.array('h', list(range(-6, 6))) m = memoryview(a) self.assertEqual(m, a) self.assertEqual(m.tolist(), a.tolist()) a = a[2::3] m = m[2::3] self.assertEqual(m, a) self.assertEqual(m.tolist(), a.tolist()) ex = ndarray(list(range(2*3*5*7*11)), shape=[11,2,7,3,5], format='L') m = memoryview(ex) self.assertEqual(m.tolist(), ex.tolist()) ex = ndarray([(2, 5), (7, 11)], shape=[2], format='lh') m = memoryview(ex) self.assertRaises(NotImplementedError, m.tolist) ex = ndarray([b'12345'], shape=[1], format="s") m = memoryview(ex) self.assertRaises(NotImplementedError, m.tolist) ex = ndarray([b"a",b"b",b"c",b"d",b"e",b"f"], shape=[2,3], format='s') m = memoryview(ex) self.assertRaises(NotImplementedError, m.tolist) def test_memoryview_repr(self): m = memoryview(bytearray(9)) r = m.__repr__() self.assertTrue(r.startswith("l:x:>l:y:}" class BEPoint(ctypes.BigEndianStructure): _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] point = BEPoint(100, 200) a = memoryview(point) b = memoryview(point) self.assertNotEqual(a, b) self.assertNotEqual(a, point) self.assertNotEqual(point, a) self.assertRaises(NotImplementedError, a.tolist) @warnings_helper.ignore_warnings(category=DeprecationWarning) # gh-80480 array('u') def test_memoryview_compare_special_cases_deprecated_u_type_code(self): # Depends on issue #15625: the struct module does not understand 'u'. a = array.array('u', 'xyz') v = memoryview(a) self.assertNotEqual(a, v) self.assertNotEqual(v, a) def test_memoryview_compare_ndim_zero(self): nd1 = ndarray(1729, shape=[], format='@L') nd2 = ndarray(1729, shape=[], format='L', flags=ND_WRITABLE) v = memoryview(nd1) w = memoryview(nd2) self.assertEqual(v, w) self.assertEqual(w, v) self.assertEqual(v, nd2) self.assertEqual(nd2, v) self.assertEqual(w, nd1) self.assertEqual(nd1, w) self.assertFalse(v.__ne__(w)) self.assertFalse(w.__ne__(v)) w[()] = 1728 self.assertNotEqual(v, w) self.assertNotEqual(w, v) self.assertNotEqual(v, nd2) self.assertNotEqual(nd2, v) self.assertNotEqual(w, nd1) self.assertNotEqual(nd1, w) self.assertFalse(v.__eq__(w)) self.assertFalse(w.__eq__(v)) nd = ndarray(list(range(12)), shape=[12], flags=ND_WRITABLE|ND_PIL) ex = ndarray(list(range(12)), shape=[12], flags=ND_WRITABLE|ND_PIL) m = memoryview(ex) self.assertEqual(m, nd) m[9] = 100 self.assertNotEqual(m, nd) # struct module: equal nd1 = ndarray((1729, 1.2, b'12345'), shape=[], format='Lf5s') nd2 = ndarray((1729, 1.2, b'12345'), shape=[], format='hf5s', flags=ND_WRITABLE) v = memoryview(nd1) w = memoryview(nd2) self.assertEqual(v, w) self.assertEqual(w, v) self.assertEqual(v, nd2) self.assertEqual(nd2, v) self.assertEqual(w, nd1) self.assertEqual(nd1, w) # struct module: not equal nd1 = ndarray((1729, 1.2, b'12345'), shape=[], format='Lf5s') nd2 = ndarray((-1729, 1.2, b'12345'), shape=[], format='hf5s', flags=ND_WRITABLE) v = memoryview(nd1) w = memoryview(nd2) self.assertNotEqual(v, w) self.assertNotEqual(w, v) self.assertNotEqual(v, nd2) self.assertNotEqual(nd2, v) self.assertNotEqual(w, nd1) self.assertNotEqual(nd1, w) self.assertEqual(v, nd1) self.assertEqual(w, nd2) def test_memoryview_compare_ndim_one(self): # contiguous nd1 = ndarray([-529, 576, -625, 676, -729], shape=[5], format='@h') nd2 = ndarray([-529, 576, -625, 676, 729], shape=[5], format='@h') v = memoryview(nd1) w = memoryview(nd2) self.assertEqual(v, nd1) self.assertEqual(w, nd2) self.assertNotEqual(v, nd2) self.assertNotEqual(w, nd1) self.assertNotEqual(v, w) # contiguous, struct module nd1 = ndarray([-529, 576, -625, 676, -729], shape=[5], format='', '!']: x = ndarray([2**63]*120, shape=[3,5,2,2,2], format=byteorder+'Q') y = ndarray([2**63]*120, shape=[3,5,2,2,2], format=byteorder+'Q', flags=ND_WRITABLE|ND_FORTRAN) y[2][3][1][1][1] = 1 a = memoryview(x) b = memoryview(y) self.assertEqual(a, x) self.assertEqual(b, y) self.assertNotEqual(a, b) self.assertNotEqual(a, y) self.assertNotEqual(b, x) x = ndarray([(2**63, 2**31, 2**15)]*120, shape=[3,5,2,2,2], format=byteorder+'QLH') y = ndarray([(2**63, 2**31, 2**15)]*120, shape=[3,5,2,2,2], format=byteorder+'QLH', flags=ND_WRITABLE|ND_FORTRAN) y[2][3][1][1][1] = (1, 1, 1) a = memoryview(x) b = memoryview(y) self.assertEqual(a, x) self.assertEqual(b, y) self.assertNotEqual(a, b) self.assertNotEqual(a, y) self.assertNotEqual(b, x) def test_memoryview_check_released(self): a = array.array('d', [1.1, 2.2, 3.3]) m = memoryview(a) m.release() # PyMemoryView_FromObject() self.assertRaises(ValueError, memoryview, m) # memoryview.cast() self.assertRaises(ValueError, m.cast, 'c') # memoryview.__iter__() self.assertRaises(ValueError, m.__iter__) # getbuffer() self.assertRaises(ValueError, ndarray, m) # memoryview.tolist() self.assertRaises(ValueError, m.tolist) # memoryview.tobytes() self.assertRaises(ValueError, m.tobytes) # sequence self.assertRaises(ValueError, eval, "1.0 in m", locals()) # subscript self.assertRaises(ValueError, m.__getitem__, 0) # assignment self.assertRaises(ValueError, m.__setitem__, 0, 1) for attr in ('obj', 'nbytes', 'readonly', 'itemsize', 'format', 'ndim', 'shape', 'strides', 'suboffsets', 'c_contiguous', 'f_contiguous', 'contiguous'): self.assertRaises(ValueError, m.__getattribute__, attr) # richcompare b = array.array('d', [1.1, 2.2, 3.3]) m1 = memoryview(a) m2 = memoryview(b) self.assertEqual(m1, m2) m1.release() self.assertNotEqual(m1, m2) self.assertNotEqual(m1, a) self.assertEqual(m1, m1) def test_memoryview_tobytes(self): # Many implicit tests are already in self.verify(). t = (-529, 576, -625, 676, -729) nd = ndarray(t, shape=[5], format='@h') m = memoryview(nd) self.assertEqual(m, nd) self.assertEqual(m.tobytes(), nd.tobytes()) nd = ndarray([t], shape=[1], format='>hQiLl') m = memoryview(nd) self.assertEqual(m, nd) self.assertEqual(m.tobytes(), nd.tobytes()) nd = ndarray([t for _ in range(12)], shape=[2,2,3], format='=hQiLl') m = memoryview(nd) self.assertEqual(m, nd) self.assertEqual(m.tobytes(), nd.tobytes()) nd = ndarray([t for _ in range(120)], shape=[5,2,2,3,2], format='l:x:>l:y:}" class BEPoint(ctypes.BigEndianStructure): _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] point = BEPoint(100, 200) a = memoryview(point) self.assertEqual(a.tobytes(), bytes(point)) def test_memoryview_get_contiguous(self): # Many implicit tests are already in self.verify(). # no buffer interface self.assertRaises(TypeError, get_contiguous, {}, PyBUF_READ, 'F') # writable request to read-only object self.assertRaises(BufferError, get_contiguous, b'x', PyBUF_WRITE, 'C') # writable request to non-contiguous object nd = ndarray([1, 2, 3], shape=[2], strides=[2]) self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'A') # scalar, read-only request from read-only exporter nd = ndarray(9, shape=(), format="L") for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(m, nd) self.assertEqual(m[()], 9) # scalar, read-only request from writable exporter nd = ndarray(9, shape=(), format="L", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(m, nd) self.assertEqual(m[()], 9) # scalar, writable request for order in ['C', 'F', 'A']: nd[()] = 9 m = get_contiguous(nd, PyBUF_WRITE, order) self.assertEqual(m, nd) self.assertEqual(m[()], 9) m[()] = 10 self.assertEqual(m[()], 10) self.assertEqual(nd[()], 10) # zeros in shape nd = ndarray([1], shape=[0], format="L", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_READ, order) self.assertRaises(IndexError, m.__getitem__, 0) self.assertEqual(m, nd) self.assertEqual(m.tolist(), []) nd = ndarray(list(range(8)), shape=[2, 0, 7], format="L", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(ndarray(m).tolist(), [[], []]) # one-dimensional nd = ndarray([1], shape=[1], format="h", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_WRITE, order) self.assertEqual(m, nd) self.assertEqual(m.tolist(), nd.tolist()) nd = ndarray([1, 2, 3], shape=[3], format="b", flags=ND_WRITABLE) for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_WRITE, order) self.assertEqual(m, nd) self.assertEqual(m.tolist(), nd.tolist()) # one-dimensional, non-contiguous nd = ndarray([1, 2, 3], shape=[2], strides=[2], flags=ND_WRITABLE) for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(m, nd) self.assertEqual(m.tolist(), nd.tolist()) self.assertRaises(TypeError, m.__setitem__, 1, 20) self.assertEqual(m[1], 3) self.assertEqual(nd[1], 3) nd = nd[::-1] for order in ['C', 'F', 'A']: m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(m, nd) self.assertEqual(m.tolist(), nd.tolist()) self.assertRaises(TypeError, m.__setitem__, 1, 20) self.assertEqual(m[1], 1) self.assertEqual(nd[1], 1) # multi-dimensional, contiguous input nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE) for order in ['C', 'A']: m = get_contiguous(nd, PyBUF_WRITE, order) self.assertEqual(ndarray(m).tolist(), nd.tolist()) self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'F') m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(ndarray(m).tolist(), nd.tolist()) nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_FORTRAN) for order in ['F', 'A']: m = get_contiguous(nd, PyBUF_WRITE, order) self.assertEqual(ndarray(m).tolist(), nd.tolist()) self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, 'C') m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(ndarray(m).tolist(), nd.tolist()) # multi-dimensional, non-contiguous input nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL) for order in ['C', 'F', 'A']: self.assertRaises(BufferError, get_contiguous, nd, PyBUF_WRITE, order) m = get_contiguous(nd, PyBUF_READ, order) self.assertEqual(ndarray(m).tolist(), nd.tolist()) # flags nd = ndarray([1,2,3,4,5], shape=[3], strides=[2]) m = get_contiguous(nd, PyBUF_READ, 'C') self.assertTrue(m.c_contiguous) def test_memoryview_serializing(self): # C-contiguous size = struct.calcsize('i') a = array.array('i', [1,2,3,4,5]) m = memoryview(a) buf = io.BytesIO(m) b = bytearray(5*size) buf.readinto(b) self.assertEqual(m.tobytes(), b) # C-contiguous, multi-dimensional size = struct.calcsize('L') nd = ndarray(list(range(12)), shape=[2,3,2], format="L") m = memoryview(nd) buf = io.BytesIO(m) b = bytearray(2*3*2*size) buf.readinto(b) self.assertEqual(m.tobytes(), b) # Fortran contiguous, multi-dimensional #size = struct.calcsize('L') #nd = ndarray(list(range(12)), shape=[2,3,2], format="L", # flags=ND_FORTRAN) #m = memoryview(nd) #buf = io.BytesIO(m) #b = bytearray(2*3*2*size) #buf.readinto(b) #self.assertEqual(m.tobytes(), b) def test_memoryview_hash(self): # bytes exporter b = bytes(list(range(12))) m = memoryview(b) self.assertEqual(hash(b), hash(m)) # C-contiguous mc = m.cast('c', shape=[3,4]) self.assertEqual(hash(mc), hash(b)) # non-contiguous mx = m[::-2] b = bytes(list(range(12))[::-2]) self.assertEqual(hash(mx), hash(b)) # Fortran contiguous nd = ndarray(list(range(30)), shape=[3,2,5], flags=ND_FORTRAN) m = memoryview(nd) self.assertEqual(hash(m), hash(nd)) # multi-dimensional slice nd = ndarray(list(range(30)), shape=[3,2,5]) x = nd[::2, ::, ::-1] m = memoryview(x) self.assertEqual(hash(m), hash(x)) # multi-dimensional slice with suboffsets nd = ndarray(list(range(30)), shape=[2,5,3], flags=ND_PIL) x = nd[::2, ::, ::-1] m = memoryview(x) self.assertEqual(hash(m), hash(x)) # equality-hash invariant x = ndarray(list(range(12)), shape=[12], format='B') a = memoryview(x) y = ndarray(list(range(12)), shape=[12], format='b') b = memoryview(y) self.assertEqual(a, b) self.assertEqual(hash(a), hash(b)) # non-byte formats nd = ndarray(list(range(12)), shape=[2,2,3], format='L') m = memoryview(nd) self.assertRaises(ValueError, m.__hash__) nd = ndarray(list(range(-6, 6)), shape=[2,2,3], format='h') m = memoryview(nd) self.assertRaises(ValueError, m.__hash__) nd = ndarray(list(range(12)), shape=[2,2,3], format='= L') m = memoryview(nd) self.assertRaises(ValueError, m.__hash__) nd = ndarray(list(range(-6, 6)), shape=[2,2,3], format='< h') m = memoryview(nd) self.assertRaises(ValueError, m.__hash__) def test_memoryview_release(self): # Create re-exporter from getbuffer(memoryview), then release the view. a = bytearray([1,2,3]) m = memoryview(a) nd = ndarray(m) # re-exporter self.assertRaises(BufferError, m.release) del nd m.release() a = bytearray([1,2,3]) m = memoryview(a) nd1 = ndarray(m, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) self.assertIs(nd2.obj, m) self.assertRaises(BufferError, m.release) del nd1, nd2 m.release() # chained views a = bytearray([1,2,3]) m1 = memoryview(a) m2 = memoryview(m1) nd = ndarray(m2) # re-exporter m1.release() self.assertRaises(BufferError, m2.release) del nd m2.release() a = bytearray([1,2,3]) m1 = memoryview(a) m2 = memoryview(m1) nd1 = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) nd2 = ndarray(nd1, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) self.assertIs(nd2.obj, m2) m1.release() self.assertRaises(BufferError, m2.release) del nd1, nd2 m2.release() # Allow changing layout while buffers are exported. nd = ndarray([1,2,3], shape=[3], flags=ND_VAREXPORT) m1 = memoryview(nd) nd.push([4,5,6,7,8], shape=[5]) # mutate nd m2 = memoryview(nd) x = memoryview(m1) self.assertEqual(x.tolist(), m1.tolist()) y = memoryview(m2) self.assertEqual(y.tolist(), m2.tolist()) self.assertEqual(y.tolist(), nd.tolist()) m2.release() y.release() nd.pop() # pop the current view self.assertEqual(x.tolist(), nd.tolist()) del nd m1.release() x.release() # If multiple memoryviews share the same managed buffer, implicit # release() in the context manager's __exit__() method should still # work. def catch22(b): with memoryview(b) as m2: pass x = bytearray(b'123') with memoryview(x) as m1: catch22(m1) self.assertEqual(m1[0], ord(b'1')) x = ndarray(list(range(12)), shape=[2,2,3], format='l') y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) self.assertIs(z.obj, x) with memoryview(z) as m: catch22(m) self.assertEqual(m[0:1].tolist(), [[[0, 1, 2], [3, 4, 5]]]) # Test garbage collection. for flags in (0, ND_REDIRECT): x = bytearray(b'123') with memoryview(x) as m1: del x y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags) with memoryview(y) as m2: del y z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags) with memoryview(z) as m3: del z catch22(m3) catch22(m2) catch22(m1) self.assertEqual(m1[0], ord(b'1')) self.assertEqual(m2[1], ord(b'2')) self.assertEqual(m3[2], ord(b'3')) del m3 del m2 del m1 x = bytearray(b'123') with memoryview(x) as m1: del x y = ndarray(m1, getbuf=PyBUF_FULL_RO, flags=flags) with memoryview(y) as m2: del y z = ndarray(m2, getbuf=PyBUF_FULL_RO, flags=flags) with memoryview(z) as m3: del z catch22(m1) catch22(m2) catch22(m3) self.assertEqual(m1[0], ord(b'1')) self.assertEqual(m2[1], ord(b'2')) self.assertEqual(m3[2], ord(b'3')) del m1, m2, m3 # memoryview.release() fails if the view has exported buffers. x = bytearray(b'123') with self.assertRaises(BufferError): with memoryview(x) as m: ex = ndarray(m) m[0] == ord(b'1') def test_memoryview_redirect(self): nd = ndarray([1.0 * x for x in range(12)], shape=[12], format='d') a = array.array('d', [1.0 * x for x in range(12)]) for x in (nd, a): y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) m = memoryview(z) self.assertIs(y.obj, x) self.assertIs(z.obj, x) self.assertIs(m.obj, x) self.assertEqual(m, x) self.assertEqual(m, y) self.assertEqual(m, z) self.assertEqual(m[1:3], x[1:3]) self.assertEqual(m[1:3], y[1:3]) self.assertEqual(m[1:3], z[1:3]) del y, z self.assertEqual(m[1:3], x[1:3]) def test_memoryview_from_static_exporter(self): fmt = 'B' lst = [0,1,2,3,4,5,6,7,8,9,10,11] # exceptions self.assertRaises(TypeError, staticarray, 1, 2, 3) # view.obj==x x = staticarray() y = memoryview(x) self.verify(y, obj=x, itemsize=1, fmt=fmt, readonly=True, ndim=1, shape=[12], strides=[1], lst=lst) for i in range(12): self.assertEqual(y[i], i) del x del y x = staticarray() y = memoryview(x) del y del x x = staticarray() y = ndarray(x, getbuf=PyBUF_FULL_RO) z = ndarray(y, getbuf=PyBUF_FULL_RO) m = memoryview(z) self.assertIs(y.obj, x) self.assertIs(m.obj, z) self.verify(m, obj=z, itemsize=1, fmt=fmt, readonly=True, ndim=1, shape=[12], strides=[1], lst=lst) del x, y, z, m x = staticarray() y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) m = memoryview(z) self.assertIs(y.obj, x) self.assertIs(z.obj, x) self.assertIs(m.obj, x) self.verify(m, obj=x, itemsize=1, fmt=fmt, readonly=True, ndim=1, shape=[12], strides=[1], lst=lst) del x, y, z, m # view.obj==NULL x = staticarray(legacy_mode=True) y = memoryview(x) self.verify(y, obj=None, itemsize=1, fmt=fmt, readonly=True, ndim=1, shape=[12], strides=[1], lst=lst) for i in range(12): self.assertEqual(y[i], i) del x del y x = staticarray(legacy_mode=True) y = memoryview(x) del y del x x = staticarray(legacy_mode=True) y = ndarray(x, getbuf=PyBUF_FULL_RO) z = ndarray(y, getbuf=PyBUF_FULL_RO) m = memoryview(z) self.assertIs(y.obj, None) self.assertIs(m.obj, z) self.verify(m, obj=z, itemsize=1, fmt=fmt, readonly=True, ndim=1, shape=[12], strides=[1], lst=lst) del x, y, z, m x = staticarray(legacy_mode=True) y = ndarray(x, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) z = ndarray(y, getbuf=PyBUF_FULL_RO, flags=ND_REDIRECT) m = memoryview(z) # Clearly setting view.obj==NULL is inferior, since it # messes up the redirection chain: self.assertIs(y.obj, None) self.assertIs(z.obj, y) self.assertIs(m.obj, y) self.verify(m, obj=y, itemsize=1, fmt=fmt, readonly=True, ndim=1, shape=[12], strides=[1], lst=lst) del x, y, z, m def test_memoryview_getbuffer_undefined(self): # getbufferproc does not adhere to the new documentation nd = ndarray([1,2,3], [3], flags=ND_GETBUF_FAIL|ND_GETBUF_UNDEFINED) self.assertRaises(BufferError, memoryview, nd) def test_issue_7385(self): x = ndarray([1,2,3], shape=[3], flags=ND_GETBUF_FAIL) self.assertRaises(BufferError, memoryview, x) @support.cpython_only def test_pybuffer_size_from_format(self): # basic tests for format in ('', 'ii', '3s'): self.assertEqual(_testcapi.PyBuffer_SizeFromFormat(format), struct.calcsize(format)) class TestPythonBufferProtocol(unittest.TestCase): def test_basic(self): class MyBuffer: def __buffer__(self, flags): return memoryview(b"hello") mv = memoryview(MyBuffer()) self.assertEqual(mv.tobytes(), b"hello") self.assertEqual(bytes(MyBuffer()), b"hello") def test_bad_buffer_method(self): class MustReturnMV: def __buffer__(self, flags): return 42 self.assertRaises(TypeError, memoryview, MustReturnMV()) class NoBytesEither: def __buffer__(self, flags): return b"hello" self.assertRaises(TypeError, memoryview, NoBytesEither()) class WrongArity: def __buffer__(self): return memoryview(b"hello") self.assertRaises(TypeError, memoryview, WrongArity()) def test_release_buffer(self): class WhatToRelease: def __init__(self): self.held = False self.ba = bytearray(b"hello") def __buffer__(self, flags): if self.held: raise TypeError("already held") self.held = True return memoryview(self.ba) def __release_buffer__(self, buffer): self.held = False wr = WhatToRelease() self.assertFalse(wr.held) with memoryview(wr) as mv: self.assertTrue(wr.held) self.assertEqual(mv.tobytes(), b"hello") self.assertFalse(wr.held) def test_same_buffer_returned(self): class WhatToRelease: def __init__(self): self.held = False self.ba = bytearray(b"hello") self.created_mv = None def __buffer__(self, flags): if self.held: raise TypeError("already held") self.held = True self.created_mv = memoryview(self.ba) return self.created_mv def __release_buffer__(self, buffer): assert buffer is self.created_mv self.held = False wr = WhatToRelease() self.assertFalse(wr.held) with memoryview(wr) as mv: self.assertTrue(wr.held) self.assertEqual(mv.tobytes(), b"hello") self.assertFalse(wr.held) def test_buffer_flags(self): class PossiblyMutable: def __init__(self, data, mutable) -> None: self._data = bytearray(data) self._mutable = mutable def __buffer__(self, flags): if flags & inspect.BufferFlags.WRITABLE: if not self._mutable: raise RuntimeError("not mutable") return memoryview(self._data) else: return memoryview(bytes(self._data)) mutable = PossiblyMutable(b"hello", True) immutable = PossiblyMutable(b"hello", False) with memoryview._from_flags(mutable, inspect.BufferFlags.WRITABLE) as mv: self.assertEqual(mv.tobytes(), b"hello") mv[0] = ord(b'x') self.assertEqual(mv.tobytes(), b"xello") with memoryview._from_flags(mutable, inspect.BufferFlags.SIMPLE) as mv: self.assertEqual(mv.tobytes(), b"xello") with self.assertRaises(TypeError): mv[0] = ord(b'h') self.assertEqual(mv.tobytes(), b"xello") with memoryview._from_flags(immutable, inspect.BufferFlags.SIMPLE) as mv: self.assertEqual(mv.tobytes(), b"hello") with self.assertRaises(TypeError): mv[0] = ord(b'x') self.assertEqual(mv.tobytes(), b"hello") with self.assertRaises(RuntimeError): memoryview._from_flags(immutable, inspect.BufferFlags.WRITABLE) with memoryview(immutable) as mv: self.assertEqual(mv.tobytes(), b"hello") with self.assertRaises(TypeError): mv[0] = ord(b'x') self.assertEqual(mv.tobytes(), b"hello") def test_call_builtins(self): ba = bytearray(b"hello") mv = ba.__buffer__(0) self.assertEqual(mv.tobytes(), b"hello") ba.__release_buffer__(mv) with self.assertRaises(OverflowError): ba.__buffer__(sys.maxsize + 1) @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_buffer(self): buf = _testcapi.testBuf() self.assertEqual(buf.references, 0) mv = buf.__buffer__(0) self.assertIsInstance(mv, memoryview) self.assertEqual(mv.tobytes(), b"test") self.assertEqual(buf.references, 1) buf.__release_buffer__(mv) self.assertEqual(buf.references, 0) with self.assertRaises(ValueError): mv.tobytes() # Calling it again doesn't cause issues with self.assertRaises(ValueError): buf.__release_buffer__(mv) self.assertEqual(buf.references, 0) @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_buffer_invalid_flags(self): buf = _testcapi.testBuf() self.assertRaises(SystemError, buf.__buffer__, PyBUF_READ) self.assertRaises(SystemError, buf.__buffer__, PyBUF_WRITE) @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_fill_buffer_invalid_flags(self): # PyBuffer_FillInfo source = b"abc" self.assertRaises(SystemError, _testcapi.buffer_fill_info, source, 0, PyBUF_READ) self.assertRaises(SystemError, _testcapi.buffer_fill_info, source, 0, PyBUF_WRITE) @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_c_fill_buffer_readonly_and_writable(self): source = b"abc" with _testcapi.buffer_fill_info(source, 1, PyBUF_SIMPLE) as m: self.assertEqual(bytes(m), b"abc") self.assertTrue(m.readonly) with _testcapi.buffer_fill_info(source, 0, PyBUF_WRITABLE) as m: self.assertEqual(bytes(m), b"abc") self.assertFalse(m.readonly) self.assertRaises(BufferError, _testcapi.buffer_fill_info, source, 1, PyBUF_WRITABLE) def test_inheritance(self): class A(bytearray): def __buffer__(self, flags): return super().__buffer__(flags) a = A(b"hello") mv = memoryview(a) self.assertEqual(mv.tobytes(), b"hello") def test_inheritance_releasebuffer(self): rb_call_count = 0 class B(bytearray): def __buffer__(self, flags): return super().__buffer__(flags) def __release_buffer__(self, view): nonlocal rb_call_count rb_call_count += 1 super().__release_buffer__(view) b = B(b"hello") with memoryview(b) as mv: self.assertEqual(mv.tobytes(), b"hello") self.assertEqual(rb_call_count, 0) self.assertEqual(rb_call_count, 1) def test_inherit_but_return_something_else(self): class A(bytearray): def __buffer__(self, flags): return memoryview(b"hello") a = A(b"hello") with memoryview(a) as mv: self.assertEqual(mv.tobytes(), b"hello") rb_call_count = 0 rb_raised = False class B(bytearray): def __buffer__(self, flags): return memoryview(b"hello") def __release_buffer__(self, view): nonlocal rb_call_count rb_call_count += 1 try: super().__release_buffer__(view) except ValueError: nonlocal rb_raised rb_raised = True b = B(b"hello") with memoryview(b) as mv: self.assertEqual(mv.tobytes(), b"hello") self.assertEqual(rb_call_count, 0) self.assertEqual(rb_call_count, 1) self.assertIs(rb_raised, True) def test_override_only_release(self): class C(bytearray): def __release_buffer__(self, buffer): super().__release_buffer__(buffer) c = C(b"hello") with memoryview(c) as mv: self.assertEqual(mv.tobytes(), b"hello") def test_release_saves_reference(self): smuggled_buffer = None class C(bytearray): def __release_buffer__(s, buffer: memoryview): with self.assertRaises(ValueError): memoryview(buffer) with self.assertRaises(ValueError): buffer.cast("b") with self.assertRaises(ValueError): buffer.toreadonly() with self.assertRaises(ValueError): buffer[:1] with self.assertRaises(ValueError): buffer.__buffer__(0) nonlocal smuggled_buffer smuggled_buffer = buffer self.assertEqual(buffer.tobytes(), b"hello") super().__release_buffer__(buffer) c = C(b"hello") with memoryview(c) as mv: self.assertEqual(mv.tobytes(), b"hello") c.clear() with self.assertRaises(ValueError): smuggled_buffer.tobytes() def test_release_saves_reference_no_subclassing(self): ba = bytearray(b"hello") class C: def __buffer__(self, flags): return memoryview(ba) def __release_buffer__(self, buffer): self.buffer = buffer c = C() with memoryview(c) as mv: self.assertEqual(mv.tobytes(), b"hello") self.assertEqual(c.buffer.tobytes(), b"hello") with self.assertRaises(BufferError): ba.clear() c.buffer.release() ba.clear() def test_multiple_inheritance_buffer_last(self): class A: def __buffer__(self, flags): return memoryview(b"hello A") class B(A, bytearray): def __buffer__(self, flags): return super().__buffer__(flags) b = B(b"hello") with memoryview(b) as mv: self.assertEqual(mv.tobytes(), b"hello A") class Releaser: def __release_buffer__(self, buffer): self.buffer = buffer class C(Releaser, bytearray): def __buffer__(self, flags): return super().__buffer__(flags) c = C(b"hello C") with memoryview(c) as mv: self.assertEqual(mv.tobytes(), b"hello C") c.clear() with self.assertRaises(ValueError): c.buffer.tobytes() def test_multiple_inheritance_buffer_last_raising(self): class A: def __buffer__(self, flags): raise RuntimeError("should not be called") def __release_buffer__(self, buffer): raise RuntimeError("should not be called") class B(bytearray, A): def __buffer__(self, flags): return super().__buffer__(flags) b = B(b"hello") with memoryview(b) as mv: self.assertEqual(mv.tobytes(), b"hello") class Releaser: buffer = None def __release_buffer__(self, buffer): self.buffer = buffer class C(bytearray, Releaser): def __buffer__(self, flags): return super().__buffer__(flags) c = C(b"hello") with memoryview(c) as mv: self.assertEqual(mv.tobytes(), b"hello") c.clear() self.assertIs(c.buffer, None) def test_release_buffer_with_exception_set(self): class A: def __buffer__(self, flags): return memoryview(bytes(8)) def __release_buffer__(self, view): pass b = bytearray(8) with memoryview(b): # now b.extend will raise an exception due to exports with self.assertRaises(BufferError): b.extend(A()) if __name__ == "__main__": unittest.main()