(original) (raw)

changeset: 73669:215690b782f8 user: Antoine Pitrou solipsis@pitrou.net date: Mon Nov 21 20:46:33 2011 +0100 files: 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 description: Issue #13411: memoryview objects are now hashable when the underlying object is hashable. diff -r 8e6c4acaf530 -r 215690b782f8 Doc/library/stdtypes.rst --- a/Doc/library/stdtypes.rst Mon Nov 21 20:39:13 2011 +0100 +++ b/Doc/library/stdtypes.rst Mon Nov 21 20:46:33 2011 +0100 @@ -2401,6 +2401,19 @@ 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() diff -r 8e6c4acaf530 -r 215690b782f8 Include/memoryobject.h --- a/Include/memoryobject.h Mon Nov 21 20:39:13 2011 +0100 +++ b/Include/memoryobject.h Mon Nov 21 20:46:33 2011 +0100 @@ -69,6 +69,7 @@ typedef struct { PyObject_HEAD Py_buffer view; + Py_hash_t hash; } PyMemoryViewObject; #endif diff -r 8e6c4acaf530 -r 215690b782f8 Include/object.h --- a/Include/object.h Mon Nov 21 20:39:13 2011 +0100 +++ b/Include/object.h Mon Nov 21 20:46:33 2011 +0100 @@ -519,6 +519,7 @@ #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 */ diff -r 8e6c4acaf530 -r 215690b782f8 Lib/test/test_memoryview.py --- a/Lib/test/test_memoryview.py Mon Nov 21 20:39:13 2011 +0100 +++ b/Lib/test/test_memoryview.py Mon Nov 21 20:46:33 2011 +0100 @@ -283,6 +283,33 @@ 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. diff -r 8e6c4acaf530 -r 215690b782f8 Lib/test/test_sys.py --- a/Lib/test/test_sys.py Mon Nov 21 20:39:13 2011 +0100 +++ b/Lib/test/test_sys.py Mon Nov 21 20:46:33 2011 +0100 @@ -770,8 +770,8 @@ 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 diff -r 8e6c4acaf530 -r 215690b782f8 Misc/NEWS --- a/Misc/NEWS Mon Nov 21 20:39:13 2011 +0100 +++ b/Misc/NEWS Mon Nov 21 20:46:33 2011 +0100 @@ -10,6 +10,9 @@ 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. diff -r 8e6c4acaf530 -r 215690b782f8 Objects/bytesobject.c --- a/Objects/bytesobject.c Mon Nov 21 20:39:13 2011 +0100 +++ b/Objects/bytesobject.c Mon Nov 21 20:46:33 2011 +0100 @@ -860,22 +860,11 @@ 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* diff -r 8e6c4acaf530 -r 215690b782f8 Objects/memoryobject.c --- a/Objects/memoryobject.c Mon Nov 21 20:39:13 2011 +0100 +++ b/Objects/memoryobject.c Mon Nov 21 20:46:33 2011 +0100 @@ -84,6 +84,7 @@ 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 @@ return PyUnicode_FromFormat("", 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 @@ 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 */ diff -r 8e6c4acaf530 -r 215690b782f8 Objects/object.c --- a/Objects/object.c Mon Nov 21 20:39:13 2011 +0100 +++ b/Objects/object.c Mon Nov 21 20:46:33 2011 +0100 @@ -744,6 +744,21 @@ } 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) { PyErr_Format(PyExc_TypeError, "unhashable type: '%.200s'",/solipsis@pitrou.net