[pypy-commit] pypy refactor-buffer-api: use a readonly flag on buffers rather than separate classes

bdkearns noreply at buildbot.pypy.org
Tue Apr 22 20:34:07 CEST 2014


Author: Brian Kearns <bdkearns at gmail.com>
Branch: refactor-buffer-api
Changeset: r70864:a7949cb30472
Date: 2014-03-25 19:20 -0400
http://bitbucket.org/pypy/pypy/changeset/a7949cb30472/

Log:	use a readonly flag on buffers rather than separate classes

diff --git a/pypy/interpreter/buffer.py b/pypy/interpreter/buffer.py
--- a/pypy/interpreter/buffer.py
+++ b/pypy/interpreter/buffer.py
@@ -6,7 +6,8 @@
 
 class Buffer(object):
     """Abstract base class for buffers."""
-    __slots__ = []
+    __slots__ = ['readonly']
+    _immutable_ = True
 
     def getlength(self):
         raise NotImplementedError
@@ -24,20 +25,6 @@
         # May be overridden.  No bounds checks.
         return ''.join([self.getitem(i) for i in range(start, stop, step)])
 
-    def get_raw_address(self):
-        raise ValueError("no raw buffer")
-
-    def is_writable(self):
-        return False
-
-
-class RWBuffer(Buffer):
-    """Abstract base class for read-write buffers."""
-    __slots__ = []
-
-    def is_writable(self):
-        return True
-
     def setitem(self, index, char):
         "Write a character into the buffer."
         raise NotImplementedError   # Must be overriden.  No bounds checks.
@@ -47,12 +34,20 @@
         for i in range(len(string)):
             self.setitem(start + i, string[i])
 
+    def get_raw_address(self):
+        raise ValueError("no raw buffer")
+
+    def is_writable(self):
+        return not self.readonly
+
 
 class StringBuffer(Buffer):
     __slots__ = ['value']
+    _immutable_ = True
 
     def __init__(self, value):
         self.value = value
+        self.readonly = True
 
     def getlength(self):
         return len(self.value)
@@ -70,13 +65,14 @@
             assert 0 <= start <= stop
             return self.value[start:stop]
         return "".join([self.value[start + i*step] for i in xrange(size)])
-# ____________________________________________________________
 
 
-class SubBufferMixin(object):
-    _attrs_ = ['buffer', 'offset', 'size']
+class SubBuffer(Buffer):
+    __slots__ = ['buffer', 'offset', 'size']
+    _immutable_ = True
 
     def __init__(self, buffer, offset, size):
+        self.readonly = buffer.readonly
         self.buffer = buffer
         self.offset = offset
         self.size = size
@@ -100,14 +96,6 @@
         return self.buffer.getslice(self.offset + start, self.offset + stop,
                                     step, size)
 
-
-class SubBuffer(Buffer):
-    import_from_mixin(SubBufferMixin)
-
-
-class RWSubBuffer(RWBuffer):
-    import_from_mixin(SubBufferMixin)
-
     def setitem(self, index, char):
         self.buffer.setitem(self.offset + index, char)
 
diff --git a/pypy/module/__pypy__/bytebuffer.py b/pypy/module/__pypy__/bytebuffer.py
--- a/pypy/module/__pypy__/bytebuffer.py
+++ b/pypy/module/__pypy__/bytebuffer.py
@@ -2,13 +2,16 @@
 # A convenient read-write buffer.  Located here for want of a better place.
 #
 
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from pypy.interpreter.gateway import unwrap_spec
 
 
-class ByteBuffer(RWBuffer):
+class ByteBuffer(Buffer):
+    _immutable_ = True
+
     def __init__(self, len):
         self.data = ['\x00'] * len
+        self.readonly = False
 
     def getlength(self):
         return len(self.data)
diff --git a/pypy/module/_cffi_backend/cbuffer.py b/pypy/module/_cffi_backend/cbuffer.py
--- a/pypy/module/_cffi_backend/cbuffer.py
+++ b/pypy/module/_cffi_backend/cbuffer.py
@@ -1,4 +1,4 @@
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from pypy.interpreter.error import oefmt
 from pypy.interpreter.gateway import unwrap_spec, interp2app
 from pypy.interpreter.typedef import TypeDef, make_weakref_descr
@@ -10,12 +10,13 @@
 from rpython.rtyper.lltypesystem.rstr import copy_string_to_raw
 
 
-class LLBuffer(RWBuffer):
+class LLBuffer(Buffer):
     _immutable_ = True
 
     def __init__(self, raw_cdata, size):
         self.raw_cdata = raw_cdata
         self.size = size
+        self.readonly = False
 
     def getlength(self):
         return self.size
@@ -32,7 +33,7 @@
     def getslice(self, start, stop, step, size):
         if step == 1:
             return rffi.charpsize2str(rffi.ptradd(self.raw_cdata, start), size)
-        return RWBuffer.getslice(self, start, stop, step, size)
+        return Buffer.getslice(self, start, stop, step, size)
 
     def setslice(self, start, string):
         raw_cdata = rffi.ptradd(self.raw_cdata, start)
diff --git a/pypy/module/_io/interp_bufferedio.py b/pypy/module/_io/interp_bufferedio.py
--- a/pypy/module/_io/interp_bufferedio.py
+++ b/pypy/module/_io/interp_bufferedio.py
@@ -4,7 +4,7 @@
 from pypy.interpreter.typedef import (
     TypeDef, GetSetProperty, generic_new_descr, interp_attrproperty_w)
 from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from rpython.rlib.rstring import StringBuilder
 from rpython.rlib.rarithmetic import r_longlong, intmask
 from rpython.rlib import rposix
@@ -101,11 +101,14 @@
     readinto = interp2app(W_BufferedIOBase.readinto_w),
 )
 
-class RawBuffer(RWBuffer):
+class RawBuffer(Buffer):
+    _immutable_ = True
+
     def __init__(self, buf, start, length):
         self.buf = buf
         self.start = start
         self.length = length
+        self.readonly = False
 
     def getlength(self):
         return self.length
diff --git a/pypy/module/_rawffi/buffer.py b/pypy/module/_rawffi/buffer.py
--- a/pypy/module/_rawffi/buffer.py
+++ b/pypy/module/_rawffi/buffer.py
@@ -1,12 +1,14 @@
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 
 # XXX not the most efficient implementation
 
 
-class RawFFIBuffer(RWBuffer):
+class RawFFIBuffer(Buffer):
+    _immutable_ = True
 
     def __init__(self, datainstance):
         self.datainstance = datainstance
+        self.readonly = False
 
     def getlength(self):
         return self.datainstance.getrawsize()
diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py
--- a/pypy/module/array/interp_array.py
+++ b/pypy/module/array/interp_array.py
@@ -9,7 +9,7 @@
 from rpython.rtyper.lltypesystem.rstr import copy_string_to_raw
 
 from pypy.interpreter.baseobjspace import W_Root
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.gateway import (
     interp2app, interpindirect2app, unwrap_spec)
@@ -133,10 +133,10 @@
         self.allocated = 0
 
     def readbuf_w(self, space):
-        return ArrayBuffer(self)
+        return ArrayBuffer(self, True)
 
     def writebuf_w(self, space):
-        return ArrayBuffer(self)
+        return ArrayBuffer(self, False)
 
     def descr_append(self, space, w_x):
         """ append(x)
@@ -586,9 +586,12 @@
     v.typecode = k
 unroll_typecodes = unrolling_iterable(types.keys())
 
-class ArrayBuffer(RWBuffer):
-    def __init__(self, array):
+class ArrayBuffer(Buffer):
+    _immutable_ = True
+
+    def __init__(self, array, readonly):
         self.array = array
+        self.readonly = readonly
 
     def getlength(self):
         return self.array.len * self.array.itemsize
diff --git a/pypy/module/array/test/test_array.py b/pypy/module/array/test/test_array.py
--- a/pypy/module/array/test/test_array.py
+++ b/pypy/module/array/test/test_array.py
@@ -421,12 +421,8 @@
     def test_buffer_write(self):
         a = self.array('c', 'hello')
         buf = buffer(a)
-        print repr(buf)
-        try:
-            buf[3] = 'L'
-        except TypeError:
-            skip("buffer(array) returns a read-only buffer on CPython")
-        assert a.tostring() == 'helLo'
+        exc = raises(TypeError, "buf[3] = 'L'")
+        assert str(exc.value) == "buffer is read-only"
 
     def test_buffer_keepalive(self):
         buf = buffer(self.array('c', 'text'))
diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py
--- a/pypy/module/cpyext/slotdefs.py
+++ b/pypy/module/cpyext/slotdefs.py
@@ -230,11 +230,13 @@
 
 class CPyBuffer(Buffer):
     # Similar to Py_buffer
+    _immutable_ = True
 
     def __init__(self, ptr, size, w_obj):
         self.ptr = ptr
         self.size = size
         self.w_obj = w_obj # kept alive
+        self.readonly = True
 
     def getlength(self):
         return self.size
diff --git a/pypy/module/cpyext/test/test_arraymodule.py b/pypy/module/cpyext/test/test_arraymodule.py
--- a/pypy/module/cpyext/test/test_arraymodule.py
+++ b/pypy/module/cpyext/test/test_arraymodule.py
@@ -53,8 +53,11 @@
     def test_buffer(self):
         module = self.import_module(name='array')
         arr = module.array('i', [1,2,3,4])
+        buf = buffer(arr)
+        exc = raises(TypeError, "buf[1] = '1'")
+        assert str(exc.value) == "buffer is read-only"
         # XXX big-endian
-        assert str(buffer(arr)) == ('\x01\0\0\0'
-                                    '\x02\0\0\0'
-                                    '\x03\0\0\0'
-                                    '\x04\0\0\0')
+        assert str(buf) == ('\x01\0\0\0'
+                            '\x02\0\0\0'
+                            '\x03\0\0\0'
+                            '\x04\0\0\0')
diff --git a/pypy/module/micronumpy/concrete.py b/pypy/module/micronumpy/concrete.py
--- a/pypy/module/micronumpy/concrete.py
+++ b/pypy/module/micronumpy/concrete.py
@@ -1,4 +1,4 @@
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from pypy.interpreter.error import OperationError, oefmt
 from rpython.rlib import jit
 from rpython.rlib.debug import make_sure_not_resized
@@ -314,8 +314,8 @@
     def get_storage(self):
         return self.storage
 
-    def get_buffer(self, space):
-        return ArrayBuffer(self)
+    def get_buffer(self, space, readonly):
+        return ArrayBuffer(self, readonly)
 
     def astype(self, space, dtype):
         strides, backstrides = calc_strides(self.get_shape(), dtype,
@@ -469,9 +469,12 @@
         free_raw_storage(self.storage)
 
 
-class ArrayBuffer(RWBuffer):
-    def __init__(self, impl):
+class ArrayBuffer(Buffer):
+    _immutable_ = True
+
+    def __init__(self, impl, readonly):
         self.impl = impl
+        self.readonly = readonly
 
     def getitem(self, item):
         return raw_storage_getitem(lltype.Char, self.impl.storage, item)
diff --git a/pypy/module/micronumpy/ndarray.py b/pypy/module/micronumpy/ndarray.py
--- a/pypy/module/micronumpy/ndarray.py
+++ b/pypy/module/micronumpy/ndarray.py
@@ -603,19 +603,19 @@
             "ctypes not implemented yet"))
 
     def buffer_w(self, space, flags):
-        return self.implementation.get_buffer(space)
+        return self.implementation.get_buffer(space, True)
 
     def readbuf_w(self, space):
-        return self.implementation.get_buffer(space)
+        return self.implementation.get_buffer(space, True)
 
     def writebuf_w(self, space):
-        return self.implementation.get_buffer(space)
+        return self.implementation.get_buffer(space, False)
 
     def charbuf_w(self, space):
-        return self.implementation.get_buffer(space).as_str()
+        return self.implementation.get_buffer(space, True).as_str()
 
     def descr_get_data(self, space):
-        return space.newbuffer(self.implementation.get_buffer(space))
+        return space.newbuffer(self.implementation.get_buffer(space, False))
 
     @unwrap_spec(offset=int, axis1=int, axis2=int)
     def descr_diagonal(self, space, offset=0, axis1=0, axis2=1):
diff --git a/pypy/module/micronumpy/test/test_ndarray.py b/pypy/module/micronumpy/test/test_ndarray.py
--- a/pypy/module/micronumpy/test/test_ndarray.py
+++ b/pypy/module/micronumpy/test/test_ndarray.py
@@ -356,6 +356,9 @@
         a = np.array([1,2,3])
         b = buffer(a)
         assert type(b) is buffer
+        assert 'read-only buffer' in repr(b)
+        exc = raises(TypeError, "b[0] = '0'")
+        assert str(exc.value) == 'buffer is read-only'
 
     def test_type(self):
         from numpypy import array
@@ -2243,6 +2246,7 @@
         a.data[4] = '\xff'
         assert a[1] == 0xff
         assert len(a.data) == 16
+        assert type(a.data) is buffer
 
     def test_explicit_dtype_conversion(self):
         from numpypy import array
diff --git a/pypy/module/mmap/interp_mmap.py b/pypy/module/mmap/interp_mmap.py
--- a/pypy/module/mmap/interp_mmap.py
+++ b/pypy/module/mmap/interp_mmap.py
@@ -2,7 +2,7 @@
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.typedef import TypeDef
 from pypy.interpreter.gateway import interp2app, unwrap_spec
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from rpython.rlib import rmmap, rarithmetic
 from rpython.rlib.rmmap import RValueError, RTypeError, RMMapError
 
@@ -19,7 +19,7 @@
 
     def readbuf_w(self, space):
         self.check_valid()
-        return MMapBuffer(self.space, self.mmap)
+        return MMapBuffer(self.space, self.mmap, True)
 
     def close(self):
         self.mmap.close()
@@ -286,10 +286,13 @@
 mmap_error._dont_inline_ = True
 
 
-class MMapBuffer(RWBuffer):
-    def __init__(self, space, mmap):
+class MMapBuffer(Buffer):
+    _immutable_ = True
+
+    def __init__(self, space, mmap, readonly):
         self.space = space
         self.mmap = mmap
+        self.readonly = readonly
 
     def getlength(self):
         return self.mmap.size
@@ -303,7 +306,7 @@
         if step == 1:
             return self.mmap.getslice(start, size)
         else:
-            return RWBuffer.getslice(self, start, stop, step, size)
+            return Buffer.getslice(self, start, stop, step, size)
 
     def setitem(self, index, char):
         self.check_valid_writeable()
@@ -313,14 +316,6 @@
         self.check_valid_writeable()
         self.mmap.setslice(start, string)
 
-    def is_writable(self):
-        try:
-            self.mmap.check_writeable()
-        except RMMapError:
-            return False
-        else:
-            return True
-
     def get_raw_address(self):
         self.check_valid()
         return self.mmap.data
diff --git a/pypy/module/mmap/test/test_mmap.py b/pypy/module/mmap/test/test_mmap.py
--- a/pypy/module/mmap/test/test_mmap.py
+++ b/pypy/module/mmap/test/test_mmap.py
@@ -560,14 +560,24 @@
         m = mmap(f.fileno(), 6)
         m[5] = '?'
         b = buffer(m)
-        try:
-            b[:3] = "FOO"
-        except TypeError:     # on CPython: "buffer is read-only" :-/
-            skip("on CPython: buffer is read-only")
+        exc = raises(TypeError, 'b[:3] = "FOO"')
+        assert str(exc.value) == "buffer is read-only"
         m.close()
         f.seek(0)
         got = f.read()
-        assert got == "FOOba?"
+        assert got == "fooba?"
+        f.close()
+
+    def test_memoryview(self):
+        from mmap import mmap
+        f = open(self.tmpname + "y", "w+")
+        f.write("foobar")
+        f.flush()
+        m = mmap(f.fileno(), 6)
+        m[5] = '?'
+        exc = raises(TypeError, memoryview, m)
+        assert 'buffer interface' in str(exc.value)
+        m.close()
         f.close()
 
     def test_offset(self):
diff --git a/pypy/objspace/std/bufferobject.py b/pypy/objspace/std/bufferobject.py
--- a/pypy/objspace/std/bufferobject.py
+++ b/pypy/objspace/std/bufferobject.py
@@ -59,10 +59,7 @@
         if size < -1:
             raise OperationError(space.w_ValueError,
                                  space.wrap("size must be zero or positive"))
-        if isinstance(buf, buffer.RWBuffer):
-            buf = buffer.RWSubBuffer(buf, offset, size)
-        else:
-            buf = buffer.SubBuffer(buf, offset, size)
+        buf = buffer.SubBuffer(buf, offset, size)
         return W_Buffer(buf)
 
     def descr_len(self, space):
@@ -77,7 +74,7 @@
 
     @unwrap_spec(newstring='bufferstr')
     def descr_setitem(self, space, w_index, newstring):
-        if not isinstance(self.buf, buffer.RWBuffer):
+        if not self.buf.is_writable():
             raise OperationError(space.w_TypeError,
                                  space.wrap("buffer is read-only"))
         _buffer_setitem(space, self.buf, w_index, newstring)
@@ -118,7 +115,7 @@
         return space.call_method(w_string, '__mul__', w_times)
 
     def descr_repr(self, space):
-        if isinstance(self.buf, buffer.RWBuffer):
+        if self.buf.is_writable():
             info = 'read-write buffer'
         else:
             info = 'read-only buffer'
diff --git a/pypy/objspace/std/bytearrayobject.py b/pypy/objspace/std/bytearrayobject.py
--- a/pypy/objspace/std/bytearrayobject.py
+++ b/pypy/objspace/std/bytearrayobject.py
@@ -5,7 +5,7 @@
 from rpython.rlib.rstring import StringBuilder
 
 from pypy.interpreter.baseobjspace import W_Root
-from pypy.interpreter.buffer import RWBuffer
+from pypy.interpreter.buffer import Buffer
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.gateway import WrappedDefault, interp2app, unwrap_spec
 from pypy.interpreter.signature import Signature
@@ -1150,9 +1150,12 @@
         start += step
 
 
-class BytearrayBuffer(RWBuffer):
+class BytearrayBuffer(Buffer):
+    _immutable_ = True
+
     def __init__(self, data):
         self.data = data
+        self.readonly = False
 
     def getlength(self):
         return len(self.data)
diff --git a/pypy/objspace/std/memoryobject.py b/pypy/objspace/std/memoryobject.py
--- a/pypy/objspace/std/memoryobject.py
+++ b/pypy/objspace/std/memoryobject.py
@@ -85,11 +85,7 @@
         size = stop - start
         if size < 0:
             size = 0
-        buf = self.buf
-        if isinstance(buf, buffer.RWBuffer):
-            buf = buffer.RWSubBuffer(buf, start, size)
-        else:
-            buf = buffer.SubBuffer(buf, start, size)
+        buf = buffer.SubBuffer(self.buf, start, size)
         return W_MemoryView(buf)
 
     def descr_tobytes(self, space):
@@ -116,7 +112,7 @@
 
     @unwrap_spec(newstring='bufferstr')
     def descr_setitem(self, space, w_index, newstring):
-        if not isinstance(self.buf, buffer.RWBuffer):
+        if not self.buf.is_writable():
             raise OperationError(space.w_TypeError,
                                  space.wrap("cannot modify read-only memory"))
         _buffer_setitem(space, self.buf, w_index, newstring)
@@ -134,7 +130,7 @@
         return space.wrap(1)
 
     def w_is_readonly(self, space):
-        return space.wrap(not isinstance(self.buf, buffer.RWBuffer))
+        return space.wrap(not self.buf.is_writable())
 
     def w_get_shape(self, space):
         return space.newtuple([space.wrap(self.getlength())])
diff --git a/pypy/objspace/std/test/test_bufferobject.py b/pypy/objspace/std/test/test_bufferobject.py
--- a/pypy/objspace/std/test/test_bufferobject.py
+++ b/pypy/objspace/std/test/test_bufferobject.py
@@ -116,23 +116,27 @@
         assert b[0] == 'w'
         assert b[:] == 'world'
         raises(IndexError, 'b[5]')
-        b[0] = 'W'
-        assert str(b) == 'World'
-        assert a.tostring() == 'hello World'
-        b[:] = '12345'
-        assert a.tostring() == 'hello 12345'
-        raises(IndexError, 'b[5] = "."')
-        b[4:2] = ''
-        assert a.tostring() == 'hello 12345'
+        exc = raises(TypeError, "b[0] = 'W'")
+        assert str(exc.value) == "buffer is read-only"
+        exc = raises(TypeError, "b[:] = '12345'")
+        assert str(exc.value) == "buffer is read-only"
+        exc = raises(TypeError, 'b[5] = "."')
+        assert str(exc.value) == "buffer is read-only"
+        exc = raises(TypeError, "b[4:2] = ''")
+        assert str(exc.value) == "buffer is read-only"
+        assert str(b) == 'world'
+        assert a.tostring() == 'hello world'
 
         b = buffer(b, 2)
         assert len(b) == 3
-        assert b[0] == '3'
-        assert b[:] == '345'
+        assert b[0] == 'r'
+        assert b[:] == 'rld'
         raises(IndexError, 'b[3]')
-        b[1] = 'X'
-        assert a.tostring() == 'hello 123X5'
-        raises(IndexError, 'b[3] = "."')
+        exc = raises(TypeError, "b[1] = 'X'")
+        assert str(exc.value) == "buffer is read-only"
+        exc = raises(TypeError, 'b[3] = "."')
+        assert str(exc.value) == "buffer is read-only"
+        assert a.tostring() == 'hello world'
 
         a = array.array("c", 'hello world')
         b = buffer(a, 1, 8)
@@ -140,28 +144,33 @@
         assert b[0] == 'e'
         assert b[:] == 'ello wor'
         raises(IndexError, 'b[8]')
-        b[0] = 'E'
-        assert str(b) == 'Ello wor'
-        assert a.tostring() == 'hEllo world'
-        b[:] = '12345678'
-        assert a.tostring() == 'h12345678ld'
-        raises(IndexError, 'b[8] = "."')
+        exc = raises(TypeError, "b[0] = 'E'")
+        assert str(exc.value) == "buffer is read-only"
+        assert str(b) == 'ello wor'
+        assert a.tostring() == 'hello world'
+        exc = raises(TypeError, "b[:] = '12345678'")
+        assert str(exc.value) == "buffer is read-only"
+        assert a.tostring() == 'hello world'
+        exc = raises(TypeError, 'b[8] = "."')
+        assert str(exc.value) == "buffer is read-only"
 
         b = buffer(b, 2, 3)
         assert len(b) == 3
-        assert b[2] == '5'
-        assert b[:] == '345'
+        assert b[2] == ' '
+        assert b[:] == 'lo '
         raises(IndexError, 'b[3]')
-        b[1] = 'X'
-        assert a.tostring() == 'h123X5678ld'
-        raises(IndexError, 'b[3] = "."')
+        exc = raises(TypeError, "b[1] = 'X'")
+        assert str(exc.value) == "buffer is read-only"
+        assert a.tostring() == 'hello world'
+        exc = raises(TypeError, 'b[3] = "."')
+        assert str(exc.value) == "buffer is read-only"
 
         b = buffer(a, 55)
         assert len(b) == 0
         assert b[:] == ''
         b = buffer(a, 6, 999)
         assert len(b) == 5
-        assert b[:] == '678ld'
+        assert b[:] == 'world'
 
         raises(ValueError, buffer, a, -1)
         raises(ValueError, buffer, a, 0, -2)


More information about the pypy-commit mailing list