Commits

Jeff Allen committed eedcfb9

memoryview: add context management, release(), hash()
Also brought in tests for this from Python 3.3 to test_memoryview and added
test of [r]split with memoryview argument to string_tests.

  • Participants
  • Parent commits b1b195f

Comments (0)

Files changed (3)

Lib/test/string_tests.py

         self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'split', buffer('//'))
         self.checkequal(['a', 'b', 'c//d'], 'a//b//c//d', 'split', buffer('//'), 2)
 
+        # by memoryview (Jython addition)
+        if test_support.is_jython:
+            # CPython does not support until v3.2
+            with memoryview('//') as target:
+                self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'split', target)
+                self.checkequal(['a', 'b', 'c//d'], 'a//b//c//d', 'split', target, 2)
+
         # mixed use of str and unicode
         self.checkequal([u'a', u'b', u'c d'], 'a b c d', 'split', u' ', 2)
 
         self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'rsplit', buffer('//'))
         self.checkequal(['a//b', 'c', 'd'], 'a//b//c//d', 'rsplit', buffer('//'), 2)
 
+        # by memoryview (Jython addition)
+        if test_support.is_jython:
+            # CPython does not support until v3.2
+            with memoryview('//') as target:
+                self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'rsplit', target)
+                self.checkequal(['a//b', 'c', 'd'], 'a//b//c//d', 'rsplit', target, 2)
+
         # mixed use of str and unicode
         self.checkequal([u'a b', u'c', u'd'], 'a b c d', 'rsplit', u' ', 2)
 

Lib/test/test_memoryview.py

             gc.collect()
             self.assertTrue(wr() is None, wr())
 
+    def _check_released(self, m, tp):   # Jython borrowed from CPython 3.3
+        check = self.assertRaises(ValueError)
+        # with check: bytes(m)     # Jython follows v2.7 behaviour
+        with check: m.tobytes()
+        with check: m.tolist()
+        with check: m[0]
+        with check: m[0] = b'x'
+        with check: len(m)
+        with check: m.format
+        with check: m.itemsize
+        with check: m.ndim
+        with check: m.readonly
+        with check: m.shape
+        with check: m.strides
+        with check:
+            with m:
+                pass
+        # str() and repr() still function
+        self.assertIn("memoryview", str(m))
+        self.assertIn("memoryview", repr(m))
+        self.assertEqual(m, m)
+        self.assertNotEqual(m, memoryview(tp(self._source)))
+        self.assertNotEqual(m, tp(self._source))
+
+    def test_contextmanager(self):      # Jython borrowed from CPython 3.3
+        for tp in self._types:
+            b = tp(self._source)
+            m = self._view(b)
+            with m as cm:
+                self.assertIs(cm, m)
+            self._check_released(m, tp)
+            m = self._view(b)
+            # Can release explicitly inside the context manager
+            with m:
+                m.release()
+
+    def test_release(self):             # Jython borrowed from CPython 3.3
+        for tp in self._types:
+            b = tp(self._source)
+            m = self._view(b)
+            m.release()
+            self._check_released(m, tp)
+            # Can be called a second time (it's a no-op)
+            m.release()
+            self._check_released(m, tp)
+
     def test_writable_readonly(self):
         # Issue #10451: memoryview incorrectly exposes a readonly
         # buffer as writable causing a segfault if using mmap
         i = io.BytesIO(b'ZZZZ')
         self.assertRaises(TypeError, i.readinto, m)
 
+    def test_getbuf_fail(self):         # Jython borrowed from CPython 3.3
+        self.assertRaises(TypeError, self._view, {})
+
+    def test_hash(self):                # Jython borrowed from CPython 3.3
+        # Memoryviews of readonly (hashable) types are hashable, and they
+        # hash as hash(obj.tobytes()).
+        tp = self.ro_type
+        if tp is None:
+            self.skipTest("no read-only type to test")
+        b = tp(self._source)
+        m = self._view(b)
+        self.assertEqual(hash(m), hash(b"abcdef"))
+        # Releasing the memoryview keeps the stored hash value (as with weakrefs)
+        m.release()
+        # XXX Hashing a released view always an error in Jython: should it be?
+        # self.assertEqual(hash(m), hash(b"abcdef"))
+
+        # Hashing a memoryview for the first time after it is released
+        # results in an error (as with weakrefs).
+        m = self._view(b)
+        m.release()
+        self.assertRaises(ValueError, hash, m)
+
+    def test_hash_writable(self):       # Jython borrowed from CPython 3.3
+        # Memoryviews of writable types are unhashable
+        tp = self.rw_type
+        if tp is None:
+            self.skipTest("no writable type to test")
+        b = tp(self._source)
+        m = self._view(b)
+        self.assertRaises(ValueError, hash, m)
+
+    @unittest.skipIf(test_support.is_jython, "GC nondeterministic in Jython")
+    def test_weakref(self):             # Jython borrowed from CPython 3.3
+        # Check memoryviews are weakrefable
+        for tp in self._types:
+            b = tp(self._source)
+            m = self._view(b)
+            L = []
+            def callback(wr, b=b):
+                L.append(b)
+            wr = weakref.ref(m, callback)
+            self.assertIs(wr(), m)
+            del m
+            test_support.gc_collect()
+            self.assertIs(wr(), None)
+            self.assertIs(L[0], b)
+
 # Variations on source objects for the buffer: bytes-like objects, then arrays
 # with itemsize > 1.
 # NOTE: support for multi-dimensional objects is unimplemented.

src/org/python/core/PyMemoryView.java

         return backing.getLen();
     }
 
+    @Override
+    public int hashCode() {
+        return memoryview___hash__();
+    }
+
+    @ExposedMethod
+    final int memoryview___hash__() {
+        checkNotReleased();
+        if (backing.isReadonly()) {
+            return backing.toString().hashCode();
+        } else {
+            throw Py.ValueError("cannot hash writable memoryview object");
+        }
+    }
+
     /*
      * ============================================================================================
      * Python API comparison operations
     /**
      * Comparison function between this memoryview and any other object. The inequality comparison
      * operators are based on this.
+     * <p>
+     * In Python 2.7, <code>memoryview</code> objects are ordered by their equivalent byte sequence
+     * values, and there is no concept of a released <code>memoryview</code>. In Python 3,
+     * <code>memoryview</code> objects are not ordered but may be tested for equality: a
+     * <code>memoryview</code> is always equal to itself, and distinct <code>memoryview</code>
+     * objects are equal if they are not released, and view equal bytes. This method supports
+     * the Python 2.7 model, and should probably not survive into Jython 3.
      *
      * @param b
      * @return 1, 0 or -1 as this>b, this==b, or this&lt;b respectively, or -2 if the comparison is
      */
     private int memoryview_cmp(PyObject b) {
 
-        // Check the memeoryview is still alive: works here for all the inequalities
+        // Check the memeryview is still alive: works here for all the inequalities
         checkNotReleased();
 
         // Try to get a byte-oriented view
      * Fail-fast comparison function between byte array types and any other object, for when the
      * test is only for equality. The inequality comparison operators <code>__eq__</code> and
      * <code>__ne__</code> are based on this.
+     * <p>
+     * In Python 2.7, <code>memoryview</code> objects are ordered by their equivalent byte sequence
+     * values, and there is no concept of a released <code>memoryview</code>. In Python 3,
+     * <code>memoryview</code> objects are not ordered but may be tested for equality: a
+     * <code>memoryview</code> is always equal to itself, and distinct <code>memoryview</code>
+     * objects are equal if they are not released, and view equal bytes. This method supports
+     * a compromise between of the two and should be rationalised in Jython 3.
      *
      * @param b
      * @return 0 if this==b, or +1 or -1 if this!=b, or -2 if the comparison is not implemented
      */
     private int memoryview_cmpeq(PyObject b) {
 
-        // Check the memeoryview is still alive: works here for all the equalities
-        checkNotReleased();
+        if (this == b) {
+            // Same object: quick success (even if released)
+            return 0;
 
-        // Try to get a byte-oriented view
-        PyBuffer bv = BaseBytes.getView(b);
+        } else if (released) {
+            // Released memoryview is not equal to anything (but not an error to have asked)
+            return -1;
 
-        if (bv == null) {
-            // Signifies a type mis-match. See PyObject._cmp_unsafe() and related code.
-            return -2;
+        } else if ((b instanceof PyMemoryView) && ((PyMemoryView)b).released) {
+            // Released memoryview is not equal to anything (but not an error to have asked)
+            return 1;
 
         } else {
 
-            try {
-                if (bv == backing) {
-                    // Same buffer: quick result
-                    return 0;
-                } else if (bv.getLen() != backing.getLen()) {
-                    // Different size: can't be equal, and we don't care which is bigger
-                    return 1;
-                } else {
-                    // Actually compare the contents
-                    return compare(backing, bv);
+            // Try to get a byte-oriented view
+            PyBuffer bv = BaseBytes.getView(b);
+
+            if (bv == null) {
+                // Signifies a type mis-match. See PyObject._cmp_unsafe() and related code.
+                return -2;
+
+            } else {
+
+                try {
+                    if (bv == backing) {
+                        // Same buffer: quick result
+                        return 0;
+                    } else if (bv.getLen() != backing.getLen()) {
+                        // Different size: can't be equal, and we don't care which is bigger
+                        return 1;
+                    } else {
+                        // Actually compare the contents
+                        return compare(backing, bv);
+                    }
+
+                } finally {
+                    // Must always let go of the buffer
+                    bv.release();
                 }
-
-            } finally {
-                // Must always let go of the buffer
-                bv.release();
             }
         }
-
     }
 
     /**
         }
     }
 
+    /**
+     * Called at the start of a context-managed suite (supporting the <code>with</code> clause).
+     *
+     * @return this object
+     */
+    public PyObject __enter__() {
+        return memoryview___enter__();
+    }
+
+    @ExposedMethod(names = "__enter__")
+    final PyObject memoryview___enter__() {
+        checkNotReleased();
+        return this;
+    }
+
+    /**
+     * Called at the end of a context-managed suite (supporting the <code>with</code> clause), and
+     * will release the <code>memoryview</code>.
+     *
+     * @return false
+     */
+    public boolean __exit__(PyObject type, PyObject value, PyObject traceback) {
+        return memoryview___exit__(type, value, traceback);
+    }
+
+    @ExposedMethod
+    final boolean memoryview___exit__(PyObject type, PyObject value, PyObject traceback) {
+        memoryview_release();
+        return false;
+    }
+
     /*
      * These strings are adapted from the patch in CPython issue 15855 and the on-line documentation
      * most attributes do not come with any docstrings in CPython 2.7, so the make_pydocs trick
      */
     @Override
     public synchronized PyBuffer getBuffer(int flags) {
+        checkNotReleased(); // Only for compatibility with CPython
         /*
          * The PyBuffer itself does all the export counting, and since the behaviour of memoryview
          * need not change, it really is a simple as:
      * <code>ValueError</code> (except <code>release()</code> itself which can be called multiple
      * times with the same effect as just one call).
      * <p>
-     * This becomes an exposed method from Python 3.2. The Jython implementation of
-     * <code>memoryview</code> follows the Python 3.3 design internally, which is the version that
-     * resolved some long-standing design issues.
+     * This becomes an exposed method in CPython from 3.2. The Jython implementation of
+     * <code>memoryview</code> follows the Python 3.3 design internally and therefore safely
+     * anticipates Python 3 in exposing <code>memoryview.release</code> along with the related
+     * context-management behaviour.
      */
     public synchronized void release() {
+        memoryview_release();
+    }
+
+    @ExposedMethod(doc = release_doc)
+    public synchronized final void memoryview_release() {
         /*
          * It is not an error to call this release method while this <code>memoryview</code> has
          * buffer exports (e.g. another <code>memoryview</code> was created on it), but it will not