Commit 6e5996d7 authored by Mark's avatar Mark

Merge pull request #117 from markflorisson88/_newaxis

Newaxis support for memoryview slices & avoid taking address of memoryviews for utility functions
parents de3f9a33 de3cdbbe
...@@ -2523,26 +2523,32 @@ class IndexNode(ExprNode): ...@@ -2523,26 +2523,32 @@ class IndexNode(ExprNode):
import MemoryView import MemoryView
skip_child_analysis = True skip_child_analysis = True
newaxes = [newaxis for newaxis in indices if newaxis.is_none]
have_slices, indices = MemoryView.unellipsify(indices, have_slices, indices = MemoryView.unellipsify(indices,
newaxes,
self.base.type.ndim) self.base.type.ndim)
self.memslice_index = len(indices) == self.base.type.ndim
self.memslice_index = (not newaxes and
len(indices) == self.base.type.ndim)
axes = [] axes = []
index_type = PyrexTypes.c_py_ssize_t_type index_type = PyrexTypes.c_py_ssize_t_type
new_indices = [] new_indices = []
if len(indices) > self.base.type.ndim: if len(indices) - len(newaxes) > self.base.type.ndim:
self.type = error_type self.type = error_type
return error(indices[self.base.type.ndim].pos, return error(indices[self.base.type.ndim].pos,
"Too many indices specified for type %s" % "Too many indices specified for type %s" %
self.base.type) self.base.type)
suboffsets_dim = -1 axis_idx = 0
for i, index in enumerate(indices[:]): for i, index in enumerate(indices[:]):
index.analyse_types(env) index.analyse_types(env)
access, packing = self.base.type.axes[i] if not index.is_none:
access, packing = self.base.type.axes[axis_idx]
axis_idx += 1
if isinstance(index, SliceNode): if isinstance(index, SliceNode):
suboffsets_dim = i
self.memslice_slice = True self.memslice_slice = True
if index.step.is_none: if index.step.is_none:
axes.append((access, packing)) axes.append((access, packing))
...@@ -2558,6 +2564,11 @@ class IndexNode(ExprNode): ...@@ -2558,6 +2564,11 @@ class IndexNode(ExprNode):
setattr(index, attr, value) setattr(index, attr, value)
new_indices.append(value) new_indices.append(value)
elif index.is_none:
self.memslice_slice = True
new_indices.append(index)
axes.append(('direct', 'strided'))
elif index.type.is_int or index.type.is_pyobject: elif index.type.is_int or index.type.is_pyobject:
if index.type.is_pyobject and not self.warned_untyped_idx: if index.type.is_pyobject and not self.warned_untyped_idx:
warning(index.pos, "Index should be typed for more " warning(index.pos, "Index should be typed for more "
...@@ -2633,6 +2644,11 @@ class IndexNode(ExprNode): ...@@ -2633,6 +2644,11 @@ class IndexNode(ExprNode):
self.index = None self.index = None
self.is_temp = True self.is_temp = True
self.use_managed_ref = True self.use_managed_ref = True
if not MemoryView.validate_axes(self.pos, axes):
self.type = error_type
return
self.type = PyrexTypes.MemoryViewSliceType( self.type = PyrexTypes.MemoryViewSliceType(
self.base.type.dtype, axes) self.base.type.dtype, axes)
......
...@@ -277,8 +277,8 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -277,8 +277,8 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
""" """
Slice a memoryviewslice. Slice a memoryviewslice.
indices - list of index nodes. If not a SliceNode, then it must be indices - list of index nodes. If not a SliceNode, or NoneNode,
coercible to Py_ssize_t then it must be coercible to Py_ssize_t
Simply call __pyx_memoryview_slice_memviewslice with the right Simply call __pyx_memoryview_slice_memviewslice with the right
arguments. arguments.
...@@ -307,28 +307,14 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -307,28 +307,14 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
code.putln("%(dst)s.memview = %(src)s.memview;" % locals()) code.putln("%(dst)s.memview = %(src)s.memview;" % locals())
code.put_incref_memoryviewslice(dst) code.put_incref_memoryviewslice(dst)
for dim, index in enumerate(indices): dim = -1
for index in indices:
error_goto = code.error_goto(index.pos) error_goto = code.error_goto(index.pos)
if not index.is_none:
if not isinstance(index, ExprNodes.SliceNode): dim += 1
# normal index
idx = index.result()
access, packing = self.type.axes[dim] access, packing = self.type.axes[dim]
if access == 'direct':
indirect = False
else:
indirect = True
generic = (access == 'full')
if new_ndim != 0:
return error(index.pos,
"All preceding dimensions must be "
"indexed and not sliced")
d = locals()
code.put(load_slice_util("SliceIndex", d))
else:
if isinstance(index, ExprNodes.SliceNode):
# slice, unspecified dimension, or part of ellipsis # slice, unspecified dimension, or part of ellipsis
d = locals() d = locals()
for s in "start stop step".split(): for s in "start stop step".split():
...@@ -344,7 +330,6 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -344,7 +330,6 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
not d['have_step']): not d['have_step']):
# full slice (:), simply copy over the extent, stride # full slice (:), simply copy over the extent, stride
# and suboffset. Also update suboffset_dim if needed # and suboffset. Also update suboffset_dim if needed
access, packing = self.type.axes[dim]
d['access'] = access d['access'] = access
code.put(load_slice_util("SimpleSlice", d)) code.put(load_slice_util("SimpleSlice", d))
else: else:
...@@ -352,6 +337,31 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry): ...@@ -352,6 +337,31 @@ class MemoryViewSliceBufferEntry(Buffer.BufferEntry):
new_ndim += 1 new_ndim += 1
elif index.is_none:
# newaxis
attribs = [('shape', 1), ('strides', 0), ('suboffsets', -1)]
for attrib, value in attribs:
code.putln("%s.%s[%d] = %d;" % (dst, attrib, new_ndim, value))
new_ndim += 1
else:
# normal index
idx = index.result()
if access == 'direct':
indirect = False
else:
indirect = True
generic = (access == 'full')
if new_ndim != 0:
return error(index.pos,
"All preceding dimensions must be "
"indexed and not sliced")
d = locals()
code.put(load_slice_util("SliceIndex", d))
if not no_suboffset_dim: if not no_suboffset_dim:
code.funcstate.release_temp(suboffset_dim) code.funcstate.release_temp(suboffset_dim)
...@@ -361,11 +371,13 @@ def empty_slice(pos): ...@@ -361,11 +371,13 @@ def empty_slice(pos):
return ExprNodes.SliceNode(pos, start=none, return ExprNodes.SliceNode(pos, start=none,
stop=none, step=none) stop=none, step=none)
def unellipsify(indices, ndim): def unellipsify(indices, newaxes, ndim):
result = [] result = []
seen_ellipsis = False seen_ellipsis = False
have_slices = False have_slices = False
n_indices = len(indices) - len(newaxes)
for index in indices: for index in indices:
if isinstance(index, ExprNodes.EllipsisNode): if isinstance(index, ExprNodes.EllipsisNode):
have_slices = True have_slices = True
...@@ -374,16 +386,19 @@ def unellipsify(indices, ndim): ...@@ -374,16 +386,19 @@ def unellipsify(indices, ndim):
if seen_ellipsis: if seen_ellipsis:
result.append(full_slice) result.append(full_slice)
else: else:
nslices = ndim - len(indices) + 1 nslices = ndim - n_indices + 1
result.extend([full_slice] * nslices) result.extend([full_slice] * nslices)
seen_ellipsis = True seen_ellipsis = True
else: else:
have_slices = have_slices or isinstance(index, ExprNodes.SliceNode) have_slices = (have_slices or
isinstance(index, ExprNodes.SliceNode) or
index.is_none)
result.append(index) result.append(index)
if len(result) < ndim: result_length = len(result) - len(newaxes)
if result_length < ndim:
have_slices = True have_slices = True
nslices = ndim - len(result) nslices = ndim - result_length
result.extend([empty_slice(indices[-1].pos)] * nslices) result.extend([empty_slice(indices[-1].pos)] * nslices)
return have_slices, result return have_slices, result
...@@ -708,6 +723,14 @@ def get_axes_specs(env, axes): ...@@ -708,6 +723,14 @@ def get_axes_specs(env, axes):
return axes_specs return axes_specs
def validate_axes(pos, axes):
if len(axes) >= Options.buffer_max_dims:
error(pos, "More dimensions than the maximum number"
" of buffer dimensions were used.")
return False
return True
def all(it): def all(it):
for item in it: for item in it:
if not item: if not item:
......
...@@ -883,10 +883,13 @@ class MemoryViewSliceTypeNode(CBaseTypeNode): ...@@ -883,10 +883,13 @@ class MemoryViewSliceTypeNode(CBaseTypeNode):
self.type = PyrexTypes.ErrorType() self.type = PyrexTypes.ErrorType()
return self.type return self.type
if not MemoryView.validate_axes(self.pos, axes_specs):
self.type = error_type
else:
MemoryView.validate_memslice_dtype(self.pos, base_type) MemoryView.validate_memslice_dtype(self.pos, base_type)
self.type = PyrexTypes.MemoryViewSliceType(base_type, axes_specs) self.type = PyrexTypes.MemoryViewSliceType(base_type, axes_specs)
self.use_memview_utilities(env) self.use_memview_utilities(env)
return self.type return self.type
def use_memview_utilities(self, env): def use_memview_utilities(self, env):
......
...@@ -645,7 +645,7 @@ class MemoryViewSliceType(PyrexType): ...@@ -645,7 +645,7 @@ class MemoryViewSliceType(PyrexType):
tup = (obj.result(), self.ndim, to_py_func, from_py_func, tup = (obj.result(), self.ndim, to_py_func, from_py_func,
self.dtype.is_pyobject) self.dtype.is_pyobject)
return "__pyx_memoryview_fromslice(&%s, %s, %s, %s, %d);" % tup return "__pyx_memoryview_fromslice(%s, %s, %s, %s, %d);" % tup
def dtype_object_conversion_funcs(self, env): def dtype_object_conversion_funcs(self, env):
import MemoryView, Code import MemoryView, Code
......
...@@ -707,8 +707,17 @@ cdef memoryview memview_slice(memoryview memview, object indices): ...@@ -707,8 +707,17 @@ cdef memoryview memview_slice(memoryview memview, object indices):
for dim, index in enumerate(indices): for dim, index in enumerate(indices):
if PyIndex_Check(index): if PyIndex_Check(index):
slice_memviewslice(p_src, p_dst, dim, new_ndim, p_suboffset_dim, slice_memviewslice(
index, 0, 0, 0, 0, 0, False) p_dst, p_src.shape[dim], p_src.strides[dim], p_src.suboffsets[dim],
dim, new_ndim, p_suboffset_dim,
index, 0, 0, # start, stop, step
0, 0, 0, # have_{start,stop,step}
False)
elif index is None:
p_dst.shape[new_ndim] = 1
p_dst.strides[new_ndim] = 0
p_dst.suboffsets[new_ndim] = -1
new_ndim += 1
else: else:
start = index.start or 0 start = index.start or 0
stop = index.stop or 0 stop = index.stop or 0
...@@ -718,18 +727,21 @@ cdef memoryview memview_slice(memoryview memview, object indices): ...@@ -718,18 +727,21 @@ cdef memoryview memview_slice(memoryview memview, object indices):
have_stop = index.stop is not None have_stop = index.stop is not None
have_step = index.step is not None have_step = index.step is not None
slice_memviewslice(p_src, p_dst, dim, new_ndim, p_suboffset_dim, slice_memviewslice(
start, stop, step, have_start, have_stop, have_step, p_dst, p_src.shape[dim], p_src.strides[dim], p_src.suboffsets[dim],
dim, new_ndim, p_suboffset_dim,
start, stop, step,
have_start, have_stop, have_step,
True) True)
new_ndim += 1 new_ndim += 1
if isinstance(memview, _memoryviewslice): if isinstance(memview, _memoryviewslice):
return memoryview_fromslice(&dst, new_ndim, return memoryview_fromslice(dst, new_ndim,
memviewsliceobj.to_object_func, memviewsliceobj.to_object_func,
memviewsliceobj.to_dtype_func, memviewsliceobj.to_dtype_func,
memview.dtype_is_object) memview.dtype_is_object)
else: else:
return memoryview_fromslice(&dst, new_ndim, NULL, NULL, return memoryview_fromslice(dst, new_ndim, NULL, NULL,
memview.dtype_is_object) memview.dtype_is_object)
...@@ -754,17 +766,12 @@ cdef extern from "pystate.h": ...@@ -754,17 +766,12 @@ cdef extern from "pystate.h":
PyObject *PyErr_Format(PyObject *exc, char *msg, ...) nogil PyObject *PyErr_Format(PyObject *exc, char *msg, ...) nogil
@cname('__pyx_memoryview_slice_memviewslice') @cname('__pyx_memoryview_slice_memviewslice')
cdef int slice_memviewslice({{memviewslice_name}} *src, cdef int slice_memviewslice(
{{memviewslice_name}} *dst, {{memviewslice_name}} *dst,
int dim, Py_ssize_t shape, Py_ssize_t stride, Py_ssize_t suboffset,
int new_ndim, int dim, int new_ndim, int *suboffset_dim,
int *suboffset_dim, Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step,
Py_ssize_t start, int have_start, int have_stop, int have_step,
Py_ssize_t stop,
Py_ssize_t step,
int have_start,
int have_stop,
int have_step,
bint is_slice) nogil except -1: bint is_slice) nogil except -1:
""" """
Create a new slice dst given slice src. Create a new slice dst given slice src.
...@@ -776,14 +783,8 @@ cdef int slice_memviewslice({{memviewslice_name}} *src, ...@@ -776,14 +783,8 @@ cdef int slice_memviewslice({{memviewslice_name}} *src,
where slicing offsets should be added where slicing offsets should be added
""" """
cdef: cdef Py_ssize_t new_shape
Py_ssize_t shape, stride, suboffset cdef bint negative_step
Py_ssize_t new_shape
bint negative_step
shape = src.shape[dim]
stride = src.strides[dim]
suboffset = src.suboffsets[dim]
if not is_slice: if not is_slice:
# index is a normal integer-like index # index is a normal integer-like index
...@@ -958,7 +959,7 @@ cdef class _memoryviewslice(memoryview): ...@@ -958,7 +959,7 @@ cdef class _memoryviewslice(memoryview):
@cname('__pyx_memoryview_fromslice') @cname('__pyx_memoryview_fromslice')
cdef memoryview_fromslice({{memviewslice_name}} *memviewslice, cdef memoryview_fromslice({{memviewslice_name}} memviewslice,
int ndim, int ndim,
object (*to_object_func)(char *), object (*to_object_func)(char *),
int (*to_dtype_func)(char *, object) except 0, int (*to_dtype_func)(char *, object) except 0,
...@@ -975,8 +976,8 @@ cdef memoryview_fromslice({{memviewslice_name}} *memviewslice, ...@@ -975,8 +976,8 @@ cdef memoryview_fromslice({{memviewslice_name}} *memviewslice,
result = _memoryviewslice(None, 0, dtype_is_object) result = _memoryviewslice(None, 0, dtype_is_object)
result.from_slice = memviewslice[0] result.from_slice = memviewslice
__PYX_INC_MEMVIEW(memviewslice, 1) __PYX_INC_MEMVIEW(&memviewslice, 1)
result.from_object = (<memoryview> memviewslice.memview).base result.from_object = (<memoryview> memviewslice.memview).base
result.typeinfo = memviewslice.memview.typeinfo result.typeinfo = memviewslice.memview.typeinfo
...@@ -1055,7 +1056,7 @@ cdef memoryview_copy_from_slice(memoryview memview, {{memviewslice_name}} *memvi ...@@ -1055,7 +1056,7 @@ cdef memoryview_copy_from_slice(memoryview memview, {{memviewslice_name}} *memvi
to_object_func = NULL to_object_func = NULL
to_dtype_func = NULL to_dtype_func = NULL
return memoryview_fromslice(memviewslice, memview.view.ndim, return memoryview_fromslice(memviewslice[0], memview.view.ndim,
to_object_func, to_dtype_func, to_object_func, to_dtype_func,
memview.dtype_is_object) memview.dtype_is_object)
......
...@@ -655,6 +655,8 @@ int {{set_function}}(const char *itemp, PyObject *obj); /* proto */ ...@@ -655,6 +655,8 @@ int {{set_function}}(const char *itemp, PyObject *obj); /* proto */
/////////////// MemviewDtypeToObject /////////////// /////////////// MemviewDtypeToObject ///////////////
{{#__pyx_memview_<dtype_name>_to_object}} {{#__pyx_memview_<dtype_name>_to_object}}
/* Convert a dtype to or from a Python object */
{{if to_py_function}} {{if to_py_function}}
PyObject *{{get_function}}(const char *itemp) { PyObject *{{get_function}}(const char *itemp) {
return (PyObject *) {{to_py_function}}(*({{dtype}} *) itemp); return (PyObject *) {{to_py_function}}(*({{dtype}} *) itemp);
...@@ -672,6 +674,7 @@ int {{set_function}}(const char *itemp, PyObject *obj) { ...@@ -672,6 +674,7 @@ int {{set_function}}(const char *itemp, PyObject *obj) {
{{endif}} {{endif}}
/////////////// MemviewObjectToObject.proto /////////////// /////////////// MemviewObjectToObject.proto ///////////////
/* Function callbacks (for memoryview object) for dtype object */
PyObject *{{get_function}}(const char *itemp); /* proto */ PyObject *{{get_function}}(const char *itemp); /* proto */
int {{set_function}}(const char *itemp, PyObject *obj); /* proto */ int {{set_function}}(const char *itemp, PyObject *obj); /* proto */
...@@ -693,8 +696,8 @@ int {{set_function}}(const char *itemp, PyObject *obj) { ...@@ -693,8 +696,8 @@ int {{set_function}}(const char *itemp, PyObject *obj) {
/* Dimension is indexed with 'start:stop:step' */ /* Dimension is indexed with 'start:stop:step' */
if (unlikely(__pyx_memoryview_slice_memviewslice( if (unlikely(__pyx_memoryview_slice_memviewslice(
&{{src}},
&{{dst}}, &{{dst}},
{{src}}.shape[{{dim}}], {{src}}.strides[{{dim}}], {{src}}.suboffsets[{{dim}}],
{{dim}}, {{dim}},
{{new_ndim}}, {{new_ndim}},
&{{suboffset_dim}}, &{{suboffset_dim}},
...@@ -793,6 +796,8 @@ __pyx_fill_slice_{{dtype_name}}({{type_decl}} *p, Py_ssize_t extent, Py_ssize_t ...@@ -793,6 +796,8 @@ __pyx_fill_slice_{{dtype_name}}({{type_decl}} *p, Py_ssize_t extent, Py_ssize_t
////////// FillStrided1DScalar ////////// ////////// FillStrided1DScalar //////////
/* Fill a slice with a scalar value. The dimension is direct and strided or contiguous */ /* Fill a slice with a scalar value. The dimension is direct and strided or contiguous */
/* This can be used as a callback for the memoryview object to efficienty assign a scalar */
/* Currently unused */
static void static void
__pyx_fill_slice_{{dtype_name}}({{type_decl}} *p, Py_ssize_t extent, Py_ssize_t stride, __pyx_fill_slice_{{dtype_name}}({{type_decl}} *p, Py_ssize_t extent, Py_ssize_t stride,
size_t itemsize, void *itemp) size_t itemsize, void *itemp)
......
...@@ -76,6 +76,22 @@ the slice can be transposed in the same way that numpy slices can be transposed: ...@@ -76,6 +76,22 @@ the slice can be transposed in the same way that numpy slices can be transposed:
This gives a new, transposed, view on the data. This gives a new, transposed, view on the data.
Newaxis
-------
New axes can be introduced by indexing an array with ``None`` ::
cdef double[:] myslice = np.linspace(0, 10, num=50)
# 2D array with shape (1, 50)
myslice[None] # or
myslice[None, :]
# 2D array with shape (50, 1)
myslice[:, None]
One may mix new axis indexing with all other forms of indexing and slicing.
See also an example_.
Specifying data layout Specifying data layout
====================== ======================
...@@ -230,3 +246,4 @@ Unlike object attributes of extension classes, memoryview slices are not initial ...@@ -230,3 +246,4 @@ Unlike object attributes of extension classes, memoryview slices are not initial
to None. to None.
.. _NumPy: http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#memory-layout .. _NumPy: http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#memory-layout
.. _example: http://www.scipy.org/Numpy_Example_List#newaxis
...@@ -54,8 +54,15 @@ cdef struct Invalid: ...@@ -54,8 +54,15 @@ cdef struct Invalid:
cdef Valid[:] validslice cdef Valid[:] validslice
cdef Invalid[:] invalidslice cdef Invalid[:] invalidslice
cdef int[:, :, :, :] four_D
four_D[None, None, None, None]
four_D[None, None, None, None, None]
cdef int[:, :, :, :, :, :, :, :] eight_D = object()
# These are VALID # These are VALID
cdef int[::view.indirect_contiguous, ::view.contiguous] a9 cdef int[::view.indirect_contiguous, ::view.contiguous] a9
four_D[None, None, None]
_ERRORS = u''' _ERRORS = u'''
11:25: Cannot specify an array that is both C and Fortran contiguous. 11:25: Cannot specify an array that is both C and Fortran contiguous.
...@@ -79,4 +86,7 @@ _ERRORS = u''' ...@@ -79,4 +86,7 @@ _ERRORS = u'''
46:35: Can only create cython.array from pointer or array 46:35: Can only create cython.array from pointer or array
47:24: Cannot assign type 'double' to 'Py_ssize_t' 47:24: Cannot assign type 'double' to 'Py_ssize_t'
55:13: Invalid base type for memoryview slice: Invalid 55:13: Invalid base type for memoryview slice: Invalid
58:6: More dimensions than the maximum number of buffer dimensions were used.
59:6: More dimensions than the maximum number of buffer dimensions were used.
61:9: More dimensions than the maximum number of buffer dimensions were used.
''' '''
...@@ -49,6 +49,14 @@ def testcase(func): ...@@ -49,6 +49,14 @@ def testcase(func):
include "mockbuffers.pxi" include "mockbuffers.pxi"
include "cythonarrayutil.pxi" include "cythonarrayutil.pxi"
def _print_attributes(memview):
print "shape: " + " ".join(map(str, memview.shape))
print "strides: " + " ".join([str(stride // memview.itemsize)
for stride in memview.strides])
print "suboffsets: " + " ".join(
[str(suboffset if suboffset < 0 else suboffset // memview.itemsize)
for suboffset in memview.suboffsets])
# #
# Buffer acquire and release tests # Buffer acquire and release tests
# #
...@@ -2217,3 +2225,63 @@ def test_inplace_assignment(): ...@@ -2217,3 +2225,63 @@ def test_inplace_assignment():
m[0] = get_int() m[0] = get_int()
print m[0] print m[0]
@testcase
def test_newaxis(int[:] one_D):
"""
>>> A = IntMockBuffer("A", range(6))
>>> test_newaxis(A)
acquired A
3
3
3
3
released A
"""
cdef int[:, :] two_D_1 = one_D[None]
cdef int[:, :] two_D_2 = one_D[None, :]
cdef int[:, :] two_D_3 = one_D[:, None]
cdef int[:, :] two_D_4 = one_D[..., None]
print two_D_1[0, 3]
print two_D_2[0, 3]
print two_D_3[3, 0]
print two_D_4[3, 0]
@testcase
def test_newaxis2(int[:, :] two_D):
"""
>>> A = IntMockBuffer("A", range(6), shape=(3, 2))
>>> test_newaxis2(A)
acquired A
shape: 3 1 1
strides: 2 0 0
suboffsets: -1 -1 -1
<BLANKLINE>
shape: 1 2 1
strides: 0 1 0
suboffsets: -1 -1 -1
<BLANKLINE>
shape: 3 1 1 1
strides: 2 0 1 0
suboffsets: -1 -1 -1 -1
<BLANKLINE>
shape: 1 2 2 1
strides: 0 2 1 0
suboffsets: -1 -1 -1 -1
released A
"""
cdef int[:, :, :] a = two_D[..., None, 1, None]
cdef int[:, :, :] b = two_D[None, 1, ..., None]
cdef int[:, :, :, :] c = two_D[..., None, 1:, None]
cdef int[:, :, :, :] d = two_D[None, 1:, ..., None]
_print_attributes(a)
print
_print_attributes(b)
print
_print_attributes(c)
print
_print_attributes(d)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment