Commit 71064916 authored by Mark Florisson's avatar Mark Florisson

Support memoryview(slice) -> NumPy coercion + NumPy-like attributes

parent dca00ef2
......@@ -11,6 +11,7 @@ cdef extern from "Python.h":
PyBUF_F_CONTIGUOUS,
PyBUF_ANY_CONTIGUOUS
PyBUF_FORMAT
PyBUF_WRITABLE
void Py_INCREF(object)
......@@ -33,6 +34,7 @@ cdef class array:
unicode mode
bytes _format
void (*callback_free_data)(char *data)
cdef object _memview
def __cinit__(array self, tuple shape, Py_ssize_t itemsize, format,
mode=u"c", bint allocate_buffer=True):
......@@ -113,6 +115,7 @@ cdef class array:
info.strides = self.strides
info.suboffsets = NULL
info.itemsize = self.itemsize
info.readonly = 0
if flags & PyBUF_FORMAT:
info.format = self.format
......@@ -138,13 +141,24 @@ cdef class array:
self.format = NULL
self.itemsize = 0
def __getitem__(self, index):
view = __pyx_memoryview_new(self, PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT)
return view[index]
property memview:
@cname('__pyx_cython_array_get_memview')
def __get__(self):
# Make this a property as 'data' may be set after instantiation
if self._memview is None:
flags = PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT|PyBUF_WRITABLE
self._memview = __pyx_memoryview_new(self, flags)
return self._memview
def __getattr__(self, attr):
return getattr(self.memview, attr)
def __setitem__(self, index, value):
view = __pyx_memoryview_new(self, PyBUF_ANY_CONTIGUOUS|PyBUF_FORMAT)
view[index] = value
def __getitem__(self, item):
return self.memview[item]
def __setitem__(self, item, value):
self.memview[item] = value
@cname("__pyx_array_new")
......@@ -165,6 +179,7 @@ import cython
# from cpython cimport ...
cdef extern from "Python.h":
int PyIndex_Check(object)
object PyLong_FromVoidPtr(void *)
cdef extern from "pythread.h":
ctypedef void *PyThread_type_lock
......@@ -244,6 +259,8 @@ cdef indirect_contiguous = Enum("<contiguous and indirect>")
cdef class memoryview(object):
cdef object obj
cdef object _size
cdef object _array_interface
cdef PyThread_type_lock lock
cdef int acquisition_count
cdef Py_buffer view
......@@ -336,6 +353,7 @@ cdef class memoryview(object):
info[0] = self.view
info.obj = self
# Some properties that have the same sematics as in NumPy
property T:
@cname('__pyx_memoryview_transpose')
def __get__(self):
......@@ -343,8 +361,8 @@ cdef class memoryview(object):
transpose_memslice(&result.from_slice)
return result
property object:
@cname('__pyx_memoryview__get__object')
property base:
@cname('__pyx_memoryview__get__base')
def __get__(self):
return self.obj
......@@ -353,23 +371,75 @@ cdef class memoryview(object):
def __get__(self):
return tuple([self.view.shape[i] for i in xrange(self.view.ndim)])
property strides:
@cname('__pyx_memoryview_get_strides')
def __get__(self):
return tuple([self.view.strides[i] for i in xrange(self.view.ndim)])
property suboffsets:
@cname('__pyx_memoryview_get_suboffsets')
def __get__(self):
return tuple([self.view.suboffsets[i] for i in xrange(self.view.ndim)])
property ndim:
@cname('__pyx_memoryview_get_ndim')
def __get__(self):
return self.view.ndim
property itemsize:
@cname('__pyx_memoryview_get_itemsize')
def __get__(self):
return self.view.itemsize
property nbytes:
@cname('__pyx_memoryview_get_nbytes')
def __get__(self):
return self.size * self.view.itemsize
property size:
@cname('__pyx_memoryview_get_size')
def __get__(self):
if self._size is None:
result = 1
for length in self.shape:
result *= length
self._size = result
return self._size
property __array_interface__:
@cname('__pyx_numpy_array_interface')
def __get__(self):
"Interface for NumPy to obtain a ndarray from this object"
# Note: we always request writable buffers, so we set readonly to
# False in the data tuple
if self._array_interface is None:
for suboffset in self.suboffsets:
if suboffset >= 0:
raise ValueError("Cannot convert indirect buffer to numpy object")
self._array_interface = dict(
data = (PyLong_FromVoidPtr(self.view.buf), False),
shape = self.shape,
strides = self.strides,
typestr = "%s%d" % (self.format, self.view.itemsize),
version = 3)
return self._array_interface
def __len__(self):
if self.view.ndim >= 1:
return self.view.shape[0]
return 0
def __repr__(self):
return "<MemoryView of %r at 0x%x>" % (self.object.__class__.__name__, id(self))
return "<MemoryView of %r at 0x%x>" % (self.base.__class__.__name__, id(self))
def __str__(self):
return "<MemoryView of %r object>" % (self.object.__class__.__name__,)
return "<MemoryView of %r object>" % (self.base.__class__.__name__,)
@cname('__pyx_memoryview_new')
......@@ -703,8 +773,8 @@ cdef class _memoryviewslice(memoryview):
else:
memoryview.assign_item_from_object(self, itemp, value)
property object:
@cname('__pyx_memoryviewslice__get__object')
property base:
@cname('__pyx_memoryviewslice__get__base')
def __get__(self):
return self.from_object
......
......@@ -154,6 +154,19 @@ These typed slices can be converted to Python objects (`cython.memoryview`), and
slicable and transposable in the same way that the slices are. They can also be converted back to typed
slices at any time.
They have the following attributes:
* shape
* strides
* suboffsets
* ndim
* size
* itemsize
* nbytes
And of course the aforementioned ``T`` attribute. These attributes have the same semantics as in NumPy_.
Cython Array
============
Whenever a slice is copied (using any of the `copy` or `copy_fortran` methods), you get a new
......@@ -186,21 +199,26 @@ You can also cast pointers to arrays::
Again, when the array will go out of scope, it will free the data, unless you register a callback like above.
Of course, you can also immidiately assign a cython.array to a typed memoryview slice.
The arrays are indexable and slicable from Python space just like memoryview objects. If you need to do this
a lot, you're better off creating a memoryview object from your array::
The arrays are indexable and slicable from Python space just like memoryview objects, and have the same
attributes as memoryview objects.
Coercion to NumPy
=================
Memoryview (and array) objects can be coerced to a NumPy ndarray, without having to copy the data. You can
e.g. do::
memview = cython.memoryview(my_cython_array, PyBUF_C_CONTIGUOUS)
cimport numpy as np
import numpy as np
# OR
numpy_array = np.asarray(<np.int32_t[:10, :10]> my_pointer)
cdef int[:, ::1] myslice = my_cython_array
memview = myslice
Of course, you are not restricted to using NumPy's type (such as ``np.int32_t`` here), you can use any usable type.
The future
==========
In the future some functionality may be added for convenience, like
1. A numpy-like `.flat` attribute (that allows efficient iteration)
2. A numpy-like `.reshape` method
3. A method `to_numpy` which would convert a memoryview object to a NumPy object
4. Indexing with newaxis or None to introduce a new axis
2. Indexing with newaxis or None to introduce a new axis
.. _NumPy: http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#memory-layout
......@@ -144,3 +144,9 @@ def test_array_from_pointer():
cdef int[:, ::1] mslice = <int[:10, :10]> getp()
print mslice[5, 6]
print (<int[:12, :10]> getp(12, 10))[5, 6]
# There is a reference cycle between the array object to its memoryview
# object that it keeps
del c_arr
import gc
gc.collect()
......@@ -331,24 +331,10 @@ cdef class LongComplexMockBuffer(MockBuffer):
cdef get_default_format(self): return b"Zg"
def print_offsets(*args, size=0, newline=True):
"""
print with a trailing comma does not have the same semantics in python 3.
Use sys.stdout.write instead.
# python 2
->> print 'foo',; sys.stdout.write('bar\n') # no space between foo and bar
foobar
In python 3 you get trailing spaces from the last ','
"""
for idx, item in enumerate(args):
if idx == len(args) - 1:
sys.stdout.write(str(item // size))
else:
sys.stdout.write('%s ' % (item // size))
if newline: sys.stdout.write('\n')
def print_offsets(*args, size, newline=True):
sys.stdout.write(' '.join([str(item // size) for item in args]))
if newline:
sys.stdout.write('\n')
def print_int_offsets(*args, newline=True):
print_offsets(*args, size=sizeof(int), newline=newline)
......
......@@ -8,6 +8,8 @@ Test slicing for memoryviews and memoryviewslices
cimport numpy as np
import numpy as np
include "cythonarrayutil.pxi"
ctypedef np.int32_t dtype_t
def get_array():
......@@ -163,3 +165,36 @@ def test_transpose():
print a[3, 2], a.T[2, 3], a_obj[3, 2], a_obj.T[2, 3], numpy_obj[3, 2], numpy_obj.T[2, 3]
def test_coerce_to_numpy():
"""
>>> test_coerce_to_numpy()
34
34
2
"""
cyarray = create_array(shape=(8, 5), mode="c", use_callback=True)
numarray = np.asarray(cyarray)
print cyarray[6, 4]
del cyarray
print numarray[6, 4]
numarray[6, 4] = 2
print numarray[6, 4]
def test_numpy_like_attributes(cyarray):
"""
>>> cyarray = create_array(shape=(8, 5), mode="c", use_callback=True)
>>> test_numpy_like_attributes(cyarray)
>>> test_numpy_like_attributes(cyarray.memview)
"""
numarray = np.asarray(cyarray)
assert cyarray.shape == numarray.shape
assert cyarray.strides == numarray.strides
assert cyarray.ndim == numarray.ndim
assert cyarray.size == numarray.size
assert cyarray.nbytes == numarray.nbytes
cdef int[:, :] mslice = numarray
assert (<object> mslice).base is numarray
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