[Jython-checkins] jython: memoryview: add context management, release(), hash()
jeff.allen
jython-checkins at python.org
Mon Oct 14 23:54:36 CEST 2013
http://hg.python.org/jython/rev/eedcfb945858
changeset: 7133:eedcfb945858
user: Jeff Allen <ja.py at farowl.co.uk>
date: Sun Oct 13 00:17:14 2013 +0100
summary:
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.
files:
Lib/test/string_tests.py | 14 +
Lib/test/test_memoryview.py | 94 ++++++++++
src/org/python/core/PyMemoryView.java | 130 +++++++++++--
3 files changed, 212 insertions(+), 26 deletions(-)
diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py
--- a/Lib/test/string_tests.py
+++ b/Lib/test/string_tests.py
@@ -424,6 +424,13 @@
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)
@@ -518,6 +525,13 @@
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)
diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -239,6 +239,52 @@
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
@@ -250,6 +296,54 @@
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.
diff --git a/src/org/python/core/PyMemoryView.java b/src/org/python/core/PyMemoryView.java
--- a/src/org/python/core/PyMemoryView.java
+++ b/src/org/python/core/PyMemoryView.java
@@ -196,6 +196,21 @@
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
@@ -279,6 +294,13 @@
/**
* 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<b respectively, or -2 if the comparison is
@@ -286,7 +308,7 @@
*/
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
@@ -319,42 +341,60 @@
* 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();
}
}
-
}
/**
@@ -468,6 +508,37 @@
}
}
+ /**
+ * 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
@@ -542,6 +613,7 @@
*/
@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:
@@ -560,11 +632,17 @@
* <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
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list