]> granicus.if.org Git - python/commitdiff
Issue #13411: memoryview objects are now hashable when the underlying object is hashable.
authorAntoine Pitrou <solipsis@pitrou.net>
Mon, 21 Nov 2011 19:46:33 +0000 (20:46 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Mon, 21 Nov 2011 19:46:33 +0000 (20:46 +0100)
Doc/library/stdtypes.rst
Include/memoryobject.h
Include/object.h
Lib/test/test_memoryview.py
Lib/test/test_sys.py
Misc/NEWS
Objects/bytesobject.c
Objects/memoryobject.c
Objects/object.c

index 334525861b1f632e264dfae2bf499f7a1868f241..cdb2a4ab0c0519f3f953dd7a6a37aff3e5a395aa 100644 (file)
@@ -2401,6 +2401,19 @@ copying.  Memory is generally interpreted as simple bytes.
 
    Notice how the size of the memoryview object cannot be changed.
 
+   Memoryviews of hashable (read-only) types are also hashable and their
+   hash value matches the corresponding bytes object::
+
+      >>> v = memoryview(b'abcefg')
+      >>> hash(v) == hash(b'abcefg')
+      True
+      >>> hash(v[2:4]) == hash(b'ce')
+      True
+
+   .. versionchanged:: 3.3
+      Memoryview objects are now hashable.
+
+
    :class:`memoryview` has several methods:
 
    .. method:: tobytes()
index 62ecbd644924b31626efab61a187a70bb8d83076..aff5d997f2172edf79748b7bb1d5b9422c7b7e29 100644 (file)
@@ -69,6 +69,7 @@ PyAPI_FUNC(PyObject *) PyMemoryView_FromBuffer(Py_buffer *info);
 typedef struct {
     PyObject_HEAD
     Py_buffer view;
+    Py_hash_t hash;
 } PyMemoryViewObject;
 #endif
 
index 648c9cbdb765a117feedc171f1904e5b46b9d73b..b97f716933a32911727e4149029080aebc426be1 100644 (file)
@@ -519,6 +519,7 @@ PyAPI_FUNC(void) Py_ReprLeave(PyObject *);
 #ifndef Py_LIMITED_API
 PyAPI_FUNC(Py_hash_t) _Py_HashDouble(double);
 PyAPI_FUNC(Py_hash_t) _Py_HashPointer(void*);
+PyAPI_FUNC(Py_hash_t) _Py_HashBytes(unsigned char*, Py_ssize_t);
 #endif
 
 /* Helper for passing objects to printf and the like */
index 0bfddd97ed646d55aed611b7a0c9f5dca6b6def9..a5a0ca1b83dc40f5ca8a8bc23b6956a878b73e58 100644 (file)
@@ -283,6 +283,33 @@ class AbstractMemoryTests:
         i = io.BytesIO(b'ZZZZ')
         self.assertRaises(TypeError, i.readinto, m)
 
+    def test_hash(self):
+        # Memoryviews of readonly (hashable) types are hashable, and they
+        # hash as the corresponding object.
+        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()
+        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):
+        # 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)
+
 # Variations on source objects for the buffer: bytes-like objects, then arrays
 # with itemsize > 1.
 # NOTE: support for multi-dimensional objects is unimplemented.
index c99f4d7e092b43c8cc7609a66e6457e1feab28ae..ba0b59210474da945b6ad7eaf6b0d438b3fc66ca 100644 (file)
@@ -770,8 +770,8 @@ class SizeofTest(unittest.TestCase):
         check(int(PyLong_BASE), size(vh) + 2*self.longdigit)
         check(int(PyLong_BASE**2-1), size(vh) + 2*self.longdigit)
         check(int(PyLong_BASE**2), size(vh) + 3*self.longdigit)
-        # memory
-        check(memoryview(b''), size(h + 'PP2P2i7P'))
+        # memory (Py_buffer + hash value)
+        check(memoryview(b''), size(h + 'PP2P2i7P' + 'P'))
         # module
         check(unittest, size(h + '3P'))
         # None
index d3b5d64bc3b97aac55c236b5e3030e1e678a1baf..faa209d351ffee2069bb2c34d4be6f5a8f215fa9 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
 Core and Builtins
 -----------------
 
+- Issue #13411: memoryview objects are now hashable when the underlying
+  object is hashable.
+
 - Issue #13338: Handle all enumerations in _Py_ANNOTATE_MEMORY_ORDER
   to allow compiling extension modules with -Wswitch-enum on gcc.
   Initial patch by Floris Bruynooghe.
index a89798a167b409cfe6b114bfaa390bc695cacf3b..88411b799bf8aa8bebdafecaa5784ddfb4f4e81c 100644 (file)
@@ -860,22 +860,11 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
 static Py_hash_t
 bytes_hash(PyBytesObject *a)
 {
-    register Py_ssize_t len;
-    register unsigned char *p;
-    register Py_uhash_t x;
-
-    if (a->ob_shash != -1)
-        return a->ob_shash;
-    len = Py_SIZE(a);
-    p = (unsigned char *) a->ob_sval;
-    x = (Py_uhash_t)*p << 7;
-    while (--len >= 0)
-        x = (1000003U*x) ^ (Py_uhash_t)*p++;
-    x ^= (Py_uhash_t)Py_SIZE(a);
-    if (x == -1)
-        x = -2;
-    a->ob_shash = x;
-    return x;
+    if (a->ob_shash == -1) {
+        /* Can't fail */
+        a->ob_shash = _Py_HashBytes((unsigned char *) a->ob_sval, Py_SIZE(a));
+    }
+    return a->ob_shash;
 }
 
 static PyObject*
index e0d1a898b30ee8c0dd7f1f1eaf282fafa74ab91a..295a742d6d39f818ec793b6b1687c1c8ad52e94b 100644 (file)
@@ -84,6 +84,7 @@ PyMemoryView_FromBuffer(Py_buffer *info)
         PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type);
     if (mview == NULL)
         return NULL;
+    mview->hash = -1;
     dup_buffer(&mview->view, info);
     /* NOTE: mview->view.obj should already have been incref'ed as
        part of PyBuffer_FillInfo(). */
@@ -512,6 +513,37 @@ memory_repr(PyMemoryViewObject *self)
         return PyUnicode_FromFormat("<memory at %p>", self);
 }
 
+static Py_hash_t
+memory_hash(PyMemoryViewObject *self)
+{
+    if (self->hash == -1) {
+        Py_buffer *view = &self->view;
+        CHECK_RELEASED_INT(self);
+        if (view->ndim > 1) {
+            PyErr_SetString(PyExc_NotImplementedError,
+                            "can't hash multi-dimensional memoryview object");
+            return -1;
+        }
+        if (view->strides && view->strides[0] != view->itemsize) {
+            PyErr_SetString(PyExc_NotImplementedError,
+                            "can't hash strided memoryview object");
+            return -1;
+        }
+        if (!view->readonly) {
+            PyErr_SetString(PyExc_ValueError,
+                            "can't hash writable memoryview object");
+            return -1;
+        }
+        if (view->obj != NULL && PyObject_Hash(view->obj) == -1) {
+            /* Keep the original error message */
+            return -1;
+        }
+        /* Can't fail */
+        self->hash = _Py_HashBytes((unsigned char *) view->buf, view->len);
+    }
+    return self->hash;
+}
+
 /* Sequence methods */
 static Py_ssize_t
 memory_length(PyMemoryViewObject *self)
@@ -829,7 +861,7 @@ PyTypeObject PyMemoryView_Type = {
     0,                                        /* tp_as_number */
     &memory_as_sequence,                      /* tp_as_sequence */
     &memory_as_mapping,                       /* tp_as_mapping */
-    0,                                        /* tp_hash */
+    (hashfunc)memory_hash,                    /* tp_hash */
     0,                                        /* tp_call */
     0,                                        /* tp_str */
     PyObject_GenericGetAttr,                  /* tp_getattro */
index 25e64e1e74a4d4574b942daa561bec1977c88669..00f171633d4c788947a37bb31aeb042cf10c4729 100644 (file)
@@ -743,6 +743,21 @@ _Py_HashPointer(void *p)
     return x;
 }
 
+Py_hash_t
+_Py_HashBytes(unsigned char *p, Py_ssize_t len)
+{
+    Py_uhash_t x;
+    Py_ssize_t i;
+
+    x = (Py_uhash_t) *p << 7;
+    for (i = 0; i < len; i++)
+        x = (1000003U * x) ^ (Py_uhash_t) *p++;
+    x ^= (Py_uhash_t) len;
+    if (x == -1)
+        x = -2;
+    return x;
+}
+
 Py_hash_t
 PyObject_HashNotImplemented(PyObject *v)
 {