Skip to content

Commit ce4a9da

Browse files
committed
Issue python#13411: memoryview objects are now hashable when the underlying object is hashable.
1 parent 0a3229d commit ce4a9da

File tree

9 files changed

+100
-19
lines changed

9 files changed

+100
-19
lines changed

Doc/library/stdtypes.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,19 @@ copying. Memory is generally interpreted as simple bytes.
24012401

24022402
Notice how the size of the memoryview object cannot be changed.
24032403

2404+
Memoryviews of hashable (read-only) types are also hashable and their
2405+
hash value matches the corresponding bytes object::
2406+
2407+
>>> v = memoryview(b'abcefg')
2408+
>>> hash(v) == hash(b'abcefg')
2409+
True
2410+
>>> hash(v[2:4]) == hash(b'ce')
2411+
True
2412+
2413+
.. versionchanged:: 3.3
2414+
Memoryview objects are now hashable.
2415+
2416+
24042417
:class:`memoryview` has several methods:
24052418

24062419
.. method:: tobytes()

Include/memoryobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ PyAPI_FUNC(PyObject *) PyMemoryView_FromBuffer(Py_buffer *info);
6969
typedef struct {
7070
PyObject_HEAD
7171
Py_buffer view;
72+
Py_hash_t hash;
7273
} PyMemoryViewObject;
7374
#endif
7475

Include/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ PyAPI_FUNC(void) Py_ReprLeave(PyObject *);
519519
#ifndef Py_LIMITED_API
520520
PyAPI_FUNC(Py_hash_t) _Py_HashDouble(double);
521521
PyAPI_FUNC(Py_hash_t) _Py_HashPointer(void*);
522+
PyAPI_FUNC(Py_hash_t) _Py_HashBytes(unsigned char*, Py_ssize_t);
522523
#endif
523524

524525
/* Helper for passing objects to printf and the like */

Lib/test/test_memoryview.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,33 @@ def test_writable_readonly(self):
283283
i = io.BytesIO(b'ZZZZ')
284284
self.assertRaises(TypeError, i.readinto, m)
285285

286+
def test_hash(self):
287+
# Memoryviews of readonly (hashable) types are hashable, and they
288+
# hash as the corresponding object.
289+
tp = self.ro_type
290+
if tp is None:
291+
self.skipTest("no read-only type to test")
292+
b = tp(self._source)
293+
m = self._view(b)
294+
self.assertEqual(hash(m), hash(b"abcdef"))
295+
# Releasing the memoryview keeps the stored hash value (as with weakrefs)
296+
m.release()
297+
self.assertEqual(hash(m), hash(b"abcdef"))
298+
# Hashing a memoryview for the first time after it is released
299+
# results in an error (as with weakrefs).
300+
m = self._view(b)
301+
m.release()
302+
self.assertRaises(ValueError, hash, m)
303+
304+
def test_hash_writable(self):
305+
# Memoryviews of writable types are unhashable
306+
tp = self.rw_type
307+
if tp is None:
308+
self.skipTest("no writable type to test")
309+
b = tp(self._source)
310+
m = self._view(b)
311+
self.assertRaises(ValueError, hash, m)
312+
286313
# Variations on source objects for the buffer: bytes-like objects, then arrays
287314
# with itemsize > 1.
288315
# NOTE: support for multi-dimensional objects is unimplemented.

Lib/test/test_sys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,8 +770,8 @@ def get_gen(): yield 1
770770
check(int(PyLong_BASE), size(vh) + 2*self.longdigit)
771771
check(int(PyLong_BASE**2-1), size(vh) + 2*self.longdigit)
772772
check(int(PyLong_BASE**2), size(vh) + 3*self.longdigit)
773-
# memory
774-
check(memoryview(b''), size(h + 'PP2P2i7P'))
773+
# memory (Py_buffer + hash value)
774+
check(memoryview(b''), size(h + 'PP2P2i7P' + 'P'))
775775
# module
776776
check(unittest, size(h + '3P'))
777777
# None

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #13411: memoryview objects are now hashable when the underlying
14+
object is hashable.
15+
1316
- Issue #13338: Handle all enumerations in _Py_ANNOTATE_MEMORY_ORDER
1417
to allow compiling extension modules with -Wswitch-enum on gcc.
1518
Initial patch by Floris Bruynooghe.

Objects/bytesobject.c

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -860,22 +860,11 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
860860
static Py_hash_t
861861
bytes_hash(PyBytesObject *a)
862862
{
863-
register Py_ssize_t len;
864-
register unsigned char *p;
865-
register Py_uhash_t x;
866-
867-
if (a->ob_shash != -1)
868-
return a->ob_shash;
869-
len = Py_SIZE(a);
870-
p = (unsigned char *) a->ob_sval;
871-
x = (Py_uhash_t)*p << 7;
872-
while (--len >= 0)
873-
x = (1000003U*x) ^ (Py_uhash_t)*p++;
874-
x ^= (Py_uhash_t)Py_SIZE(a);
875-
if (x == -1)
876-
x = -2;
877-
a->ob_shash = x;
878-
return x;
863+
if (a->ob_shash == -1) {
864+
/* Can't fail */
865+
a->ob_shash = _Py_HashBytes((unsigned char *) a->ob_sval, Py_SIZE(a));
866+
}
867+
return a->ob_shash;
879868
}
880869

881870
static PyObject*

Objects/memoryobject.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ PyMemoryView_FromBuffer(Py_buffer *info)
8484
PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type);
8585
if (mview == NULL)
8686
return NULL;
87+
mview->hash = -1;
8788
dup_buffer(&mview->view, info);
8889
/* NOTE: mview->view.obj should already have been incref'ed as
8990
part of PyBuffer_FillInfo(). */
@@ -512,6 +513,37 @@ memory_repr(PyMemoryViewObject *self)
512513
return PyUnicode_FromFormat("<memory at %p>", self);
513514
}
514515

516+
static Py_hash_t
517+
memory_hash(PyMemoryViewObject *self)
518+
{
519+
if (self->hash == -1) {
520+
Py_buffer *view = &self->view;
521+
CHECK_RELEASED_INT(self);
522+
if (view->ndim > 1) {
523+
PyErr_SetString(PyExc_NotImplementedError,
524+
"can't hash multi-dimensional memoryview object");
525+
return -1;
526+
}
527+
if (view->strides && view->strides[0] != view->itemsize) {
528+
PyErr_SetString(PyExc_NotImplementedError,
529+
"can't hash strided memoryview object");
530+
return -1;
531+
}
532+
if (!view->readonly) {
533+
PyErr_SetString(PyExc_ValueError,
534+
"can't hash writable memoryview object");
535+
return -1;
536+
}
537+
if (view->obj != NULL && PyObject_Hash(view->obj) == -1) {
538+
/* Keep the original error message */
539+
return -1;
540+
}
541+
/* Can't fail */
542+
self->hash = _Py_HashBytes((unsigned char *) view->buf, view->len);
543+
}
544+
return self->hash;
545+
}
546+
515547
/* Sequence methods */
516548
static Py_ssize_t
517549
memory_length(PyMemoryViewObject *self)
@@ -829,7 +861,7 @@ PyTypeObject PyMemoryView_Type = {
829861
0, /* tp_as_number */
830862
&memory_as_sequence, /* tp_as_sequence */
831863
&memory_as_mapping, /* tp_as_mapping */
832-
0, /* tp_hash */
864+
(hashfunc)memory_hash, /* tp_hash */
833865
0, /* tp_call */
834866
0, /* tp_str */
835867
PyObject_GenericGetAttr, /* tp_getattro */

Objects/object.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,21 @@ _Py_HashPointer(void *p)
743743
return x;
744744
}
745745

746+
Py_hash_t
747+
_Py_HashBytes(unsigned char *p, Py_ssize_t len)
748+
{
749+
Py_uhash_t x;
750+
Py_ssize_t i;
751+
752+
x = (Py_uhash_t) *p << 7;
753+
for (i = 0; i < len; i++)
754+
x = (1000003U * x) ^ (Py_uhash_t) *p++;
755+
x ^= (Py_uhash_t) len;
756+
if (x == -1)
757+
x = -2;
758+
return x;
759+
}
760+
746761
Py_hash_t
747762
PyObject_HashNotImplemented(PyObject *v)
748763
{

0 commit comments

Comments
 (0)