Commit 9ca6532b authored by scoder's avatar scoder Committed by GitHub

Merge pull request #1869 from scoder/readonly_buffers

implement read-only memoryviews
parents 83ffbe08 e4838d84
...@@ -8,10 +8,6 @@ Cython Changelog ...@@ -8,10 +8,6 @@ Cython Changelog
Features added Features added
-------------- --------------
* When compiling with gcc, the module init function is now tuned for small
code size instead of whatever compile flags were provided externally.
(Github issue #2102)
* Cdef classes can now multiply inherit from ordinary Python classes. * Cdef classes can now multiply inherit from ordinary Python classes.
(The primary base must still be a c class, possibly ``object``, and (The primary base must still be a c class, possibly ``object``, and
the other bases must *not* be cdef classes.) the other bases must *not* be cdef classes.)
...@@ -19,6 +15,13 @@ Features added ...@@ -19,6 +15,13 @@ Features added
* Type inference is now supported for Pythran compiled NumPy expressions. * Type inference is now supported for Pythran compiled NumPy expressions.
Patch by Nils Braun. (Github issue #1954) Patch by Nils Braun. (Github issue #1954)
* The ``const`` modifier can be applied to memoryview declarations to allow
read-only buffers as input. (Github issues #1605, #1869)
* When compiling with gcc, the module init function is now tuned for small
code size instead of whatever compile flags were provided externally.
(Github issue #2102)
* C file includes are moved behind the module declarations if possible, to allow * C file includes are moved behind the module declarations if possible, to allow
them to depend on module declarations themselves. them to depend on module declarations themselves.
Patch by Jeroen Demeyer. (Github issue #1896) Patch by Jeroen Demeyer. (Github issue #1896)
......
...@@ -870,16 +870,19 @@ class ExprNode(Node): ...@@ -870,16 +870,19 @@ class ExprNode(Node):
elif not src_type.is_error: elif not src_type.is_error:
error(self.pos, error(self.pos,
"Cannot convert '%s' to memoryviewslice" % (src_type,)) "Cannot convert '%s' to memoryviewslice" % (src_type,))
elif not src.type.conforms_to(dst_type, broadcast=self.is_memview_broadcast, else:
copying=self.is_memview_copy_assignment): if src.type.writable_needed:
if src.type.dtype.same_as(dst_type.dtype): dst_type.writable_needed = True
msg = "Memoryview '%s' not conformable to memoryview '%s'." if not src.type.conforms_to(dst_type, broadcast=self.is_memview_broadcast,
tup = src.type, dst_type copying=self.is_memview_copy_assignment):
else: if src.type.dtype.same_as(dst_type.dtype):
msg = "Different base types for memoryviews (%s, %s)" msg = "Memoryview '%s' not conformable to memoryview '%s'."
tup = src.type.dtype, dst_type.dtype tup = src.type, dst_type
else:
msg = "Different base types for memoryviews (%s, %s)"
tup = src.type.dtype, dst_type.dtype
error(self.pos, msg % tup) error(self.pos, msg % tup)
elif dst_type.is_pyobject: elif dst_type.is_pyobject:
if not src.type.is_pyobject: if not src.type.is_pyobject:
...@@ -4298,6 +4301,11 @@ class MemoryViewIndexNode(BufferIndexNode): ...@@ -4298,6 +4301,11 @@ class MemoryViewIndexNode(BufferIndexNode):
indices = self.indices indices = self.indices
have_slices, indices, newaxes = MemoryView.unellipsify(indices, self.base.type.ndim) have_slices, indices, newaxes = MemoryView.unellipsify(indices, self.base.type.ndim)
if not getting:
self.writable_needed = True
if self.base.is_name or self.base.is_attribute:
self.base.entry.type.writable_needed = True
self.memslice_index = (not newaxes and len(indices) == self.base.type.ndim) self.memslice_index = (not newaxes and len(indices) == self.base.type.ndim)
axes = [] axes = []
...@@ -12772,12 +12780,12 @@ class CoerceToMemViewSliceNode(CoercionNode): ...@@ -12772,12 +12780,12 @@ class CoerceToMemViewSliceNode(CoercionNode):
def generate_result_code(self, code): def generate_result_code(self, code):
self.type.create_from_py_utility_code(self.env) self.type.create_from_py_utility_code(self.env)
code.putln("%s = %s(%s);" % (self.result(), code.putln(self.type.from_py_call_code(
self.type.from_py_function, self.arg.py_result(),
self.arg.py_result())) self.result(),
self.pos,
error_cond = self.type.error_condition(self.result()) code
code.putln(code.error_goto_if(error_cond, self.pos)) ))
class CastNode(CoercionNode): class CastNode(CoercionNode):
......
...@@ -390,7 +390,7 @@ class FusedCFuncDefNode(StatListNode): ...@@ -390,7 +390,7 @@ class FusedCFuncDefNode(StatListNode):
coerce_from_py_func=memslice_type.from_py_function, coerce_from_py_func=memslice_type.from_py_function,
dtype=dtype) dtype=dtype)
decl_code.putln( decl_code.putln(
"{{memviewslice_cname}} {{coerce_from_py_func}}(object)") "{{memviewslice_cname}} {{coerce_from_py_func}}(object, int)")
pyx_code.context.update( pyx_code.context.update(
specialized_type_name=specialized_type.specialization_string, specialized_type_name=specialized_type.specialization_string,
...@@ -400,7 +400,7 @@ class FusedCFuncDefNode(StatListNode): ...@@ -400,7 +400,7 @@ class FusedCFuncDefNode(StatListNode):
u""" u"""
# try {{dtype}} # try {{dtype}}
if itemsize == -1 or itemsize == {{sizeof_dtype}}: if itemsize == -1 or itemsize == {{sizeof_dtype}}:
memslice = {{coerce_from_py_func}}(arg) memslice = {{coerce_from_py_func}}(arg, 0)
if memslice.memview: if memslice.memview:
__PYX_XDEC_MEMVIEW(&memslice, 1) __PYX_XDEC_MEMVIEW(&memslice, 1)
# print 'found a match for the buffer through format parsing' # print 'found a match for the buffer through format parsing'
......
...@@ -28,12 +28,12 @@ def concat_flags(*flags): ...@@ -28,12 +28,12 @@ def concat_flags(*flags):
format_flag = "PyBUF_FORMAT" format_flag = "PyBUF_FORMAT"
memview_c_contiguous = "(PyBUF_C_CONTIGUOUS | PyBUF_FORMAT | PyBUF_WRITABLE)" memview_c_contiguous = "(PyBUF_C_CONTIGUOUS | PyBUF_FORMAT)"
memview_f_contiguous = "(PyBUF_F_CONTIGUOUS | PyBUF_FORMAT | PyBUF_WRITABLE)" memview_f_contiguous = "(PyBUF_F_CONTIGUOUS | PyBUF_FORMAT)"
memview_any_contiguous = "(PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT | PyBUF_WRITABLE)" memview_any_contiguous = "(PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT)"
memview_full_access = "PyBUF_FULL" memview_full_access = "PyBUF_FULL_RO"
#memview_strided_access = "PyBUF_STRIDED" #memview_strided_access = "PyBUF_STRIDED_RO"
memview_strided_access = "PyBUF_RECORDS" memview_strided_access = "PyBUF_RECORDS_RO"
MEMVIEW_DIRECT = '__Pyx_MEMVIEW_DIRECT' MEMVIEW_DIRECT = '__Pyx_MEMVIEW_DIRECT'
MEMVIEW_PTR = '__Pyx_MEMVIEW_PTR' MEMVIEW_PTR = '__Pyx_MEMVIEW_PTR'
......
...@@ -3753,18 +3753,12 @@ class DefNodeWrapper(FuncDefNode): ...@@ -3753,18 +3753,12 @@ class DefNodeWrapper(FuncDefNode):
entry = arg.entry entry = arg.entry
code.putln("%s = %s;" % (entry.cname, item)) code.putln("%s = %s;" % (entry.cname, item))
else: else:
func = arg.type.from_py_function if arg.type.from_py_function:
if func:
if arg.default: if arg.default:
# C-typed default arguments must be handled here # C-typed default arguments must be handled here
code.putln('if (%s) {' % item) code.putln('if (%s) {' % item)
rhs = "%s(%s)" % (func, item) code.putln(arg.type.from_py_call_code(
if arg.type.is_enum: item, arg.entry.cname, arg.pos, code))
rhs = arg.type.cast_code(rhs)
code.putln("%s = %s; %s" % (
arg.entry.cname,
rhs,
code.error_goto_if(arg.type.error_condition(arg.entry.cname), arg.pos)))
if arg.default: if arg.default:
code.putln('} else {') code.putln('} else {')
code.putln("%s = %s;" % ( code.putln("%s = %s;" % (
...@@ -4005,17 +3999,14 @@ class DefNodeWrapper(FuncDefNode): ...@@ -4005,17 +3999,14 @@ class DefNodeWrapper(FuncDefNode):
def generate_arg_conversion_from_pyobject(self, arg, code): def generate_arg_conversion_from_pyobject(self, arg, code):
new_type = arg.type new_type = arg.type
func = new_type.from_py_function
# copied from CoerceFromPyTypeNode # copied from CoerceFromPyTypeNode
if func: if new_type.from_py_function:
lhs = arg.entry.cname code.putln(new_type.from_py_call_code(
rhs = "%s(%s)" % (func, arg.hdr_cname) arg.hdr_cname,
if new_type.is_enum: arg.entry.cname,
rhs = PyrexTypes.typecast(new_type, PyrexTypes.c_long_type, rhs) arg.pos,
code.putln("%s = %s; %s" % ( code,
lhs, ))
rhs,
code.error_goto_if(new_type.error_condition(arg.entry.cname), arg.pos)))
else: else:
error(arg.pos, "Cannot convert Python object argument to type '%s'" % new_type) error(arg.pos, "Cannot convert Python object argument to type '%s'" % new_type)
......
...@@ -2481,9 +2481,12 @@ def p_c_simple_base_type(s, self_flag, nonempty, templates = None): ...@@ -2481,9 +2481,12 @@ def p_c_simple_base_type(s, self_flag, nonempty, templates = None):
error(pos, "Expected an identifier, found '%s'" % s.sy) error(pos, "Expected an identifier, found '%s'" % s.sy)
if s.systring == 'const': if s.systring == 'const':
s.next() s.next()
base_type = p_c_base_type(s, base_type = p_c_base_type(s, self_flag=self_flag, nonempty=nonempty, templates=templates)
self_flag = self_flag, nonempty = nonempty, templates = templates) if isinstance(base_type, Nodes.MemoryViewSliceTypeNode):
return Nodes.CConstTypeNode(pos, base_type = base_type) # reverse order to avoid having to write "(const int)[:]"
base_type.base_type_node = Nodes.CConstTypeNode(pos, base_type=base_type.base_type_node)
return base_type
return Nodes.CConstTypeNode(pos, base_type=base_type)
if looking_at_base_type(s): if looking_at_base_type(s):
#print "p_c_simple_base_type: looking_at_base_type at", s.position() #print "p_c_simple_base_type: looking_at_base_type at", s.position()
is_basic = 1 is_basic = 1
......
...@@ -316,6 +316,21 @@ class PyrexType(BaseType): ...@@ -316,6 +316,21 @@ class PyrexType(BaseType):
def needs_nonecheck(self): def needs_nonecheck(self):
return 0 return 0
def _assign_from_py_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None, extra_args=None):
args = ', ' + ', '.join('%s' % arg for arg in extra_args) if extra_args else ''
convert_call = "%s(%s%s)" % (
from_py_function or self.from_py_function,
source_code,
args,
)
if self.is_enum:
convert_call = typecast(self, c_long_type, convert_call)
return '%s = %s; %s' % (
result_code,
convert_call,
code.error_goto_if(error_condition or self.error_condition(result_code), error_pos))
def public_decl(base_code, dll_linkage): def public_decl(base_code, dll_linkage):
if dll_linkage: if dll_linkage:
...@@ -493,12 +508,11 @@ class CTypedefType(BaseType): ...@@ -493,12 +508,11 @@ class CTypedefType(BaseType):
def from_py_call_code(self, source_code, result_code, error_pos, code, def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None): from_py_function=None, error_condition=None):
if from_py_function is None:
from_py_function = self.from_py_function
if error_condition is None:
error_condition = self.error_condition(result_code)
return self.typedef_base_type.from_py_call_code( return self.typedef_base_type.from_py_call_code(
source_code, result_code, error_pos, code, from_py_function, error_condition) source_code, result_code, error_pos, code,
from_py_function or self.from_py_function,
error_condition or self.error_condition(result_code)
)
def overflow_check_binop(self, binop, env, const_rhs=False): def overflow_check_binop(self, binop, env, const_rhs=False):
env.use_utility_code(UtilityCode.load("Common", "Overflow.c")) env.use_utility_code(UtilityCode.load("Common", "Overflow.c"))
...@@ -621,6 +635,7 @@ class MemoryViewSliceType(PyrexType): ...@@ -621,6 +635,7 @@ class MemoryViewSliceType(PyrexType):
def same_as_resolved_type(self, other_type): def same_as_resolved_type(self, other_type):
return ((other_type.is_memoryviewslice and return ((other_type.is_memoryviewslice and
self.writable_needed == other_type.writable_needed and
self.dtype.same_as(other_type.dtype) and self.dtype.same_as(other_type.dtype) and
self.axes == other_type.axes) or self.axes == other_type.axes) or
other_type is error_type) other_type is error_type)
...@@ -767,7 +782,18 @@ class MemoryViewSliceType(PyrexType): ...@@ -767,7 +782,18 @@ class MemoryViewSliceType(PyrexType):
src = self src = self
if src.dtype != dst.dtype: if self.writable_needed and not dst.writable_needed:
return False
src_dtype, dst_dtype = src.dtype, dst.dtype
if dst_dtype.is_const:
# Requesting read-only views is always ok => consider only the non-const base type.
dst_dtype = dst_dtype.const_base_type
if src_dtype.is_const:
# When assigning between read-only views, compare only the non-const base types.
src_dtype = src_dtype.const_base_type
if src_dtype != dst_dtype:
return False return False
if src.ndim != dst.ndim: if src.ndim != dst.ndim:
...@@ -885,11 +911,12 @@ class MemoryViewSliceType(PyrexType): ...@@ -885,11 +911,12 @@ class MemoryViewSliceType(PyrexType):
def from_py_call_code(self, source_code, result_code, error_pos, code, def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None): from_py_function=None, error_condition=None):
return '%s = %s(%s); %s' % ( # NOTE: auto-detection of readonly buffers is disabled:
result_code, # writable = self.writable_needed or not self.dtype.is_const
from_py_function or self.from_py_function, writable = not self.dtype.is_const
source_code, return self._assign_from_py_code(
code.error_goto_if(error_condition or self.error_condition(result_code), error_pos)) source_code, result_code, error_pos, code, from_py_function, error_condition,
extra_args=['PyBUF_WRITABLE' if writable else '0'])
def create_to_py_utility_code(self, env): def create_to_py_utility_code(self, env):
self._dtype_to_py_func, self._dtype_from_py_func = self.dtype_object_conversion_funcs(env) self._dtype_to_py_func, self._dtype_from_py_func = self.dtype_object_conversion_funcs(env)
...@@ -917,25 +944,29 @@ class MemoryViewSliceType(PyrexType): ...@@ -917,25 +944,29 @@ class MemoryViewSliceType(PyrexType):
if self.dtype.is_pyobject: if self.dtype.is_pyobject:
utility_name = "MemviewObjectToObject" utility_name = "MemviewObjectToObject"
else: else:
to_py = self.dtype.create_to_py_utility_code(env) self.dtype.create_to_py_utility_code(env)
from_py = self.dtype.create_from_py_utility_code(env) to_py_function = self.dtype.to_py_function
if not (to_py or from_py):
return "NULL", "NULL"
if not self.dtype.to_py_function: from_py_function = None
get_function = "NULL" if not self.dtype.is_const:
self.dtype.create_from_py_utility_code(env)
from_py_function = self.dtype.from_py_function
if not self.dtype.from_py_function: if not (to_py_function or from_py_function):
return "NULL", "NULL"
if not to_py_function:
get_function = "NULL"
if not from_py_function:
set_function = "NULL" set_function = "NULL"
utility_name = "MemviewDtypeToObject" utility_name = "MemviewDtypeToObject"
error_condition = (self.dtype.error_condition('value') or error_condition = (self.dtype.error_condition('value') or
'PyErr_Occurred()') 'PyErr_Occurred()')
context.update( context.update(
to_py_function = self.dtype.to_py_function, to_py_function=to_py_function,
from_py_function = self.dtype.from_py_function, from_py_function=from_py_function,
dtype = self.dtype.empty_declaration_code(), dtype=self.dtype.empty_declaration_code(),
error_condition = error_condition, error_condition=error_condition,
) )
utility = TempitaUtilityCode.load_cached( utility = TempitaUtilityCode.load_cached(
...@@ -1470,11 +1501,9 @@ class CType(PyrexType): ...@@ -1470,11 +1501,9 @@ class CType(PyrexType):
def from_py_call_code(self, source_code, result_code, error_pos, code, def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None): from_py_function=None, error_condition=None):
return '%s = %s(%s); %s' % ( return self._assign_from_py_code(
result_code, source_code, result_code, error_pos, code, from_py_function, error_condition)
from_py_function or self.from_py_function,
source_code,
code.error_goto_if(error_condition or self.error_condition(result_code), error_pos))
class PythranExpr(CType): class PythranExpr(CType):
...@@ -2438,6 +2467,7 @@ class CArrayType(CPointerBaseType): ...@@ -2438,6 +2467,7 @@ class CArrayType(CPointerBaseType):
def from_py_call_code(self, source_code, result_code, error_pos, code, def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None): from_py_function=None, error_condition=None):
assert not error_condition, '%s: %s' % (error_pos, error_condition)
call_code = "%s(%s, %s, %s)" % ( call_code = "%s(%s, %s, %s)" % (
from_py_function or self.from_py_function, from_py_function or self.from_py_function,
source_code, result_code, self.size) source_code, result_code, self.size)
...@@ -3879,16 +3909,6 @@ class CEnumType(CIntLike, CType): ...@@ -3879,16 +3909,6 @@ class CEnumType(CIntLike, CType):
self.name, self.cname, self.typedef_flag, namespace) self.name, self.cname, self.typedef_flag, namespace)
return self return self
def from_py_call_code(self, source_code, result_code, error_pos, code,
from_py_function=None, error_condition=None):
rhs = "%s(%s)" % (
from_py_function or self.from_py_function,
source_code)
return '%s = %s;%s' % (
result_code,
typecast(self, c_long_type, rhs),
' %s' % code.error_goto_if(error_condition or self.error_condition(result_code), error_pos))
def create_type_wrapper(self, env): def create_type_wrapper(self, env):
from .UtilityCode import CythonUtilityCode from .UtilityCode import CythonUtilityCode
env.use_utility_code(CythonUtilityCode.load( env.use_utility_code(CythonUtilityCode.load(
......
...@@ -65,6 +65,7 @@ cdef extern from *: ...@@ -65,6 +65,7 @@ cdef extern from *:
PyBUF_STRIDES PyBUF_STRIDES
PyBUF_INDIRECT PyBUF_INDIRECT
PyBUF_RECORDS PyBUF_RECORDS
PyBUF_RECORDS_RO
ctypedef struct __Pyx_TypeInfo: ctypedef struct __Pyx_TypeInfo:
pass pass
...@@ -408,6 +409,9 @@ cdef class memoryview(object): ...@@ -408,6 +409,9 @@ cdef class memoryview(object):
return self.convert_item_to_object(itemp) return self.convert_item_to_object(itemp)
def __setitem__(memoryview self, object index, object value): def __setitem__(memoryview self, object index, object value):
if self.view.readonly:
raise TypeError("Cannot assign to read-only memoryview")
have_slices, index = _unellipsify(index, self.view.ndim) have_slices, index = _unellipsify(index, self.view.ndim)
if have_slices: if have_slices:
...@@ -507,6 +511,9 @@ cdef class memoryview(object): ...@@ -507,6 +511,9 @@ cdef class memoryview(object):
@cname('getbuffer') @cname('getbuffer')
def __getbuffer__(self, Py_buffer *info, int flags): def __getbuffer__(self, Py_buffer *info, int flags):
if flags & PyBUF_WRITABLE and self.view.readonly:
raise ValueError("Cannot create writable memory view from read-only memoryview")
if flags & PyBUF_STRIDES: if flags & PyBUF_STRIDES:
info.shape = self.view.shape info.shape = self.view.shape
else: else:
...@@ -531,7 +538,7 @@ cdef class memoryview(object): ...@@ -531,7 +538,7 @@ cdef class memoryview(object):
info.ndim = self.view.ndim info.ndim = self.view.ndim
info.itemsize = self.view.itemsize info.itemsize = self.view.itemsize
info.len = self.view.len info.len = self.view.len
info.readonly = 0 info.readonly = self.view.readonly
info.obj = self info.obj = self
__pyx_getbuffer = capsule(<void *> &__pyx_memoryview_getbuffer, "getbuffer(obj, view, flags)") __pyx_getbuffer = capsule(<void *> &__pyx_memoryview_getbuffer, "getbuffer(obj, view, flags)")
...@@ -1012,7 +1019,10 @@ cdef memoryview_fromslice({{memviewslice_name}} memviewslice, ...@@ -1012,7 +1019,10 @@ cdef memoryview_fromslice({{memviewslice_name}} memviewslice,
(<__pyx_buffer *> &result.view).obj = Py_None (<__pyx_buffer *> &result.view).obj = Py_None
Py_INCREF(Py_None) Py_INCREF(Py_None)
result.flags = PyBUF_RECORDS if (<memoryview>memviewslice.memview).flags & PyBUF_WRITABLE:
result.flags = PyBUF_RECORDS
else:
result.flags = PyBUF_RECORDS_RO
result.view.shape = <Py_ssize_t *> result.from_slice.shape result.view.shape = <Py_ssize_t *> result.from_slice.shape
result.view.strides = <Py_ssize_t *> result.from_slice.strides result.view.strides = <Py_ssize_t *> result.from_slice.strides
......
...@@ -82,7 +82,7 @@ typedef volatile __pyx_atomic_int_type __pyx_atomic_int; ...@@ -82,7 +82,7 @@ typedef volatile __pyx_atomic_int_type __pyx_atomic_int;
/////////////// ObjectToMemviewSlice.proto /////////////// /////////////// ObjectToMemviewSlice.proto ///////////////
static CYTHON_INLINE {{memviewslice_name}} {{funcname}}(PyObject *); static CYTHON_INLINE {{memviewslice_name}} {{funcname}}(PyObject *, int writable_flag);
////////// MemviewSliceInit.proto ////////// ////////// MemviewSliceInit.proto //////////
...@@ -127,7 +127,7 @@ static CYTHON_INLINE char *__pyx_memviewslice_index_full( ...@@ -127,7 +127,7 @@ static CYTHON_INLINE char *__pyx_memviewslice_index_full(
/////////////// ObjectToMemviewSlice /////////////// /////////////// ObjectToMemviewSlice ///////////////
//@requires: MemviewSliceValidateAndInit //@requires: MemviewSliceValidateAndInit
static CYTHON_INLINE {{memviewslice_name}} {{funcname}}(PyObject *obj) { static CYTHON_INLINE {{memviewslice_name}} {{funcname}}(PyObject *obj, int writable_flag) {
{{memviewslice_name}} result = {{memslice_init}}; {{memviewslice_name}} result = {{memslice_init}};
__Pyx_BufFmt_StackElem stack[{{struct_nesting_depth}}]; __Pyx_BufFmt_StackElem stack[{{struct_nesting_depth}}];
int axes_specs[] = { {{axes_specs}} }; int axes_specs[] = { {{axes_specs}} };
...@@ -140,7 +140,7 @@ static CYTHON_INLINE {{memviewslice_name}} {{funcname}}(PyObject *obj) { ...@@ -140,7 +140,7 @@ static CYTHON_INLINE {{memviewslice_name}} {{funcname}}(PyObject *obj) {
} }
retcode = __Pyx_ValidateAndInit_memviewslice(axes_specs, {{c_or_f_flag}}, retcode = __Pyx_ValidateAndInit_memviewslice(axes_specs, {{c_or_f_flag}},
{{buf_flag}}, {{ndim}}, {{buf_flag}} | writable_flag, {{ndim}},
&{{dtype_typeinfo}}, stack, &{{dtype_typeinfo}}, stack,
&result, obj); &result, obj);
......
...@@ -227,6 +227,33 @@ As for NumPy, new axes can be introduced by indexing an array with ``None`` :: ...@@ -227,6 +227,33 @@ As for NumPy, new axes can be introduced by indexing an array with ``None`` ::
One may mix new axis indexing with all other forms of indexing and slicing. One may mix new axis indexing with all other forms of indexing and slicing.
See also an example_. See also an example_.
Read-only views
---------------
Since Cython 0.28, the memoryview item type can be declared as ``const`` to
support read-only buffers as input::
cdef const double[:] myslice # const item type => read-only view
a = np.linspace(0, 10, num=50)
a.setflags(write=False)
myslice = a
Note that this does not *require* the input buffer to be read-only::
a = np.linspace(0, 10, num=50)
myslice = a # read-only view of a writable buffer
Writable buffers are still accepted by ``const`` views, but read-only
buffers are not accepted for non-const, writable views::
cdef double[:] myslice # a normal read/write memory view
a = np.linspace(0, 10, num=50)
a.setflags(write=False)
myslice = a # ERROR: requesting writable memory view from read-only buffer!
Comparison to the old buffer support Comparison to the old buffer support
==================================== ====================================
......
...@@ -599,7 +599,7 @@ def readonly(obj): ...@@ -599,7 +599,7 @@ def readonly(obj):
acquired R acquired R
25 25
released R released R
>>> [str(x) for x in R.recieved_flags] # Works in both py2 and py3 >>> [str(x) for x in R.received_flags] # Works in both py2 and py3
['FORMAT', 'INDIRECT', 'ND', 'STRIDES'] ['FORMAT', 'INDIRECT', 'ND', 'STRIDES']
""" """
cdef object[unsigned short int, ndim=3] buf = obj cdef object[unsigned short int, ndim=3] buf = obj
...@@ -612,7 +612,7 @@ def writable(obj): ...@@ -612,7 +612,7 @@ def writable(obj):
>>> writable(R) >>> writable(R)
acquired R acquired R
released R released R
>>> [str(x) for x in R.recieved_flags] # Py2/3 >>> [str(x) for x in R.received_flags] # Py2/3
['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE'] ['FORMAT', 'INDIRECT', 'ND', 'STRIDES', 'WRITABLE']
""" """
cdef object[unsigned short int, ndim=3] buf = obj cdef object[unsigned short int, ndim=3] buf = obj
...@@ -626,7 +626,7 @@ def strided(object[int, ndim=1, mode='strided'] buf): ...@@ -626,7 +626,7 @@ def strided(object[int, ndim=1, mode='strided'] buf):
acquired A acquired A
released A released A
2 2
>>> [str(x) for x in A.recieved_flags] # Py2/3 >>> [str(x) for x in A.received_flags] # Py2/3
['FORMAT', 'ND', 'STRIDES'] ['FORMAT', 'ND', 'STRIDES']
Check that the suboffsets were patched back prior to release. Check that the suboffsets were patched back prior to release.
...@@ -641,7 +641,7 @@ def c_contig(object[int, ndim=1, mode='c'] buf): ...@@ -641,7 +641,7 @@ def c_contig(object[int, ndim=1, mode='c'] buf):
>>> A = IntMockBuffer(None, range(4)) >>> A = IntMockBuffer(None, range(4))
>>> c_contig(A) >>> c_contig(A)
2 2
>>> [str(x) for x in A.recieved_flags] >>> [str(x) for x in A.received_flags]
['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS'] ['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS']
""" """
return buf[2] return buf[2]
...@@ -654,7 +654,7 @@ def c_contig_2d(object[int, ndim=2, mode='c'] buf): ...@@ -654,7 +654,7 @@ def c_contig_2d(object[int, ndim=2, mode='c'] buf):
>>> A = IntMockBuffer(None, range(12), shape=(3,4)) >>> A = IntMockBuffer(None, range(12), shape=(3,4))
>>> c_contig_2d(A) >>> c_contig_2d(A)
7 7
>>> [str(x) for x in A.recieved_flags] >>> [str(x) for x in A.received_flags]
['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS'] ['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS']
""" """
return buf[1, 3] return buf[1, 3]
...@@ -665,7 +665,7 @@ def f_contig(object[int, ndim=1, mode='fortran'] buf): ...@@ -665,7 +665,7 @@ def f_contig(object[int, ndim=1, mode='fortran'] buf):
>>> A = IntMockBuffer(None, range(4)) >>> A = IntMockBuffer(None, range(4))
>>> f_contig(A) >>> f_contig(A)
2 2
>>> [str(x) for x in A.recieved_flags] >>> [str(x) for x in A.received_flags]
['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS'] ['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS']
""" """
return buf[2] return buf[2]
...@@ -678,7 +678,7 @@ def f_contig_2d(object[int, ndim=2, mode='fortran'] buf): ...@@ -678,7 +678,7 @@ def f_contig_2d(object[int, ndim=2, mode='fortran'] buf):
>>> A = IntMockBuffer(None, range(12), shape=(4,3), strides=(1, 4)) >>> A = IntMockBuffer(None, range(12), shape=(4,3), strides=(1, 4))
>>> f_contig_2d(A) >>> f_contig_2d(A)
7 7
>>> [str(x) for x in A.recieved_flags] >>> [str(x) for x in A.received_flags]
['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS'] ['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS']
""" """
return buf[3, 1] return buf[3, 1]
...@@ -1103,7 +1103,7 @@ def bufdefaults1(IntStridedMockBuffer[int, ndim=1] buf): ...@@ -1103,7 +1103,7 @@ def bufdefaults1(IntStridedMockBuffer[int, ndim=1] buf):
>>> bufdefaults1(A) >>> bufdefaults1(A)
acquired A acquired A
released A released A
>>> [str(x) for x in A.recieved_flags] >>> [str(x) for x in A.received_flags]
['FORMAT', 'ND', 'STRIDES'] ['FORMAT', 'ND', 'STRIDES']
""" """
pass pass
......
...@@ -18,16 +18,17 @@ cdef class MockBuffer: ...@@ -18,16 +18,17 @@ cdef class MockBuffer:
cdef object format, offset cdef object format, offset
cdef void* buffer cdef void* buffer
cdef Py_ssize_t len, itemsize cdef Py_ssize_t len, itemsize
cdef int ndim
cdef Py_ssize_t* strides cdef Py_ssize_t* strides
cdef Py_ssize_t* shape cdef Py_ssize_t* shape
cdef Py_ssize_t* suboffsets cdef Py_ssize_t* suboffsets
cdef object label, log cdef object label, log
cdef int ndim
cdef bint writable
cdef readonly object recieved_flags, release_ok cdef readonly object received_flags, release_ok
cdef public object fail cdef public object fail
def __init__(self, label, data, shape=None, strides=None, format=None, offset=0): def __init__(self, label, data, shape=None, strides=None, format=None, writable=True, offset=0):
# It is important not to store references to data after the constructor # It is important not to store references to data after the constructor
# as refcounting is checked on object buffers. # as refcounting is checked on object buffers.
self.label = label self.label = label
...@@ -35,6 +36,7 @@ cdef class MockBuffer: ...@@ -35,6 +36,7 @@ cdef class MockBuffer:
self.log = "" self.log = ""
self.offset = offset self.offset = offset
self.itemsize = self.get_itemsize() self.itemsize = self.get_itemsize()
self.writable = writable
if format is None: format = self.get_default_format() if format is None: format = self.get_default_format()
if shape is None: shape = (len(data),) if shape is None: shape = (len(data),)
if strides is None: if strides is None:
...@@ -127,16 +129,19 @@ cdef class MockBuffer: ...@@ -127,16 +129,19 @@ cdef class MockBuffer:
if self.fail: if self.fail:
raise ValueError("Failing on purpose") raise ValueError("Failing on purpose")
self.recieved_flags = [] self.received_flags = []
cdef int value cdef int value
for name, value in available_flags: for name, value in available_flags:
if (value & flags) == value: if (value & flags) == value:
self.recieved_flags.append(name) self.received_flags.append(name)
if flags & cpython.buffer.PyBUF_WRITABLE and not self.writable:
raise BufferError("Writable buffer requested from read-only mock: %s" % ' | '.join(self.received_flags))
buffer.buf = <void*>(<char*>self.buffer + (<int>self.offset * self.itemsize)) buffer.buf = <void*>(<char*>self.buffer + (<int>self.offset * self.itemsize))
buffer.obj = self buffer.obj = self
buffer.len = self.len buffer.len = self.len
buffer.readonly = 0 buffer.readonly = not self.writable
buffer.format = <char*>self.format buffer.format = <char*>self.format
buffer.ndim = self.ndim buffer.ndim = self.ndim
buffer.shape = self.shape buffer.shape = self.shape
......
# mode: error # mode: error
# cython: auto_pickle=False
ctypedef int[1] int_array ctypedef int[1] int_array
ctypedef int[2] int_array2 ctypedef int[2] int_array2
...@@ -30,6 +31,6 @@ d = z # not an error ...@@ -30,6 +31,6 @@ d = z # not an error
_ERRORS = u""" _ERRORS = u"""
20:0: Assignment to slice of wrong length, expected 2, got 1 21:0: Assignment to slice of wrong length, expected 2, got 1
21:0: Assignment to slice of wrong length, expected 1, got 2 22:0: Assignment to slice of wrong length, expected 1, got 2
""" """
...@@ -420,7 +420,7 @@ def writable(unsigned short int[:, :, :] mslice): ...@@ -420,7 +420,7 @@ def writable(unsigned short int[:, :, :] mslice):
>>> writable(R) >>> writable(R)
acquired R acquired R
released R released R
>>> [str(x) for x in R.recieved_flags] # Py2/3 >>> [str(x) for x in R.received_flags] # Py2/3
['FORMAT', 'ND', 'STRIDES', 'WRITABLE'] ['FORMAT', 'ND', 'STRIDES', 'WRITABLE']
""" """
buf = mslice buf = mslice
......
This diff is collapsed.
# mode: run
# tag: readonly, const, numpy
import numpy as np
def new_array():
return np.arange(10).astype('float')
ARRAY = new_array()
cdef getmax(const double[:] x):
"""Example code, should work with both ro and rw memoryviews"""
cdef double max_val = -float('inf')
for val in x:
if val > max_val:
max_val = val
return max_val
cdef update_array(double [:] x):
"""Modifying a ro memoryview should raise an error"""
x[0] = 23.
cdef getconst(const double [:] x):
"""Should accept ro memoryviews"""
return x[0]
def test_mmview_rw(x):
"""
>>> test_mmview_rw(ARRAY)
9.0
"""
return getmax(x)
def test_mmview_ro(x):
"""
>>> test_mmview_ro(new_array())
9.0
"""
x.setflags(write=False)
assert x.flags.writeable is False
return getmax(x)
def test_update_mmview_rw(x):
"""
>>> test_update_mmview_rw(new_array())
23.0
"""
update_array(x)
return x[0]
def test_update_mmview_ro(x):
"""
>>> test_update_mmview_ro(new_array())
0.0
"""
x.setflags(write=False)
assert x.flags.writeable is False
try:
update_array(x)
except ValueError: pass
else:
assert False, "RO error not raised!"
return getconst(x)
def test_rw_call_getmax(double[:] x):
"""
>>> test_rw_call_getmax(new_array())
23.0
"""
update_array(x)
assert getconst(x) == 23
return getmax(x)
def test_const_mmview_ro(x):
"""
>>> test_const_mmview_ro(new_array())
0.0
"""
x.setflags(write=False)
assert x.flags.writeable is False
return getconst(x)
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