Skip to content

Commit

Permalink
Issue #13411: memoryview objects are now hashable when the underlying…
Browse files Browse the repository at this point in the history
… object is hashable.
  • Loading branch information
pitrou committed Nov 21, 2011
1 parent 0a3229d commit ce4a9da
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 19 deletions.
13 changes: 13 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions Include/memoryobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,33 @@ def test_writable_readonly(self):
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.
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,8 +770,8 @@ def get_gen(): yield 1
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
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 5 additions & 16 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand Down
34 changes: 33 additions & 1 deletion Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(). */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 */
Expand Down
15 changes: 15 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit ce4a9da

Please sign in to comment.