Issue #13411: memoryview objects are now hashable when the underlying object is hashable.

This commit is contained in:
Antoine Pitrou 2011-11-21 20:46:33 +01:00
parent 0a3229de6b
commit ce4a9da705
9 changed files with 100 additions and 19 deletions

View File

@ -2401,6 +2401,19 @@ copying. Memory is generally interpreted as simple bytes.
Notice how the size of the memoryview object cannot be changed. 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: :class:`memoryview` has several methods:
.. method:: tobytes() .. method:: tobytes()

View File

@ -69,6 +69,7 @@ PyAPI_FUNC(PyObject *) PyMemoryView_FromBuffer(Py_buffer *info);
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
Py_buffer view; Py_buffer view;
Py_hash_t hash;
} PyMemoryViewObject; } PyMemoryViewObject;
#endif #endif

View File

@ -519,6 +519,7 @@ PyAPI_FUNC(void) Py_ReprLeave(PyObject *);
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
PyAPI_FUNC(Py_hash_t) _Py_HashDouble(double); PyAPI_FUNC(Py_hash_t) _Py_HashDouble(double);
PyAPI_FUNC(Py_hash_t) _Py_HashPointer(void*); PyAPI_FUNC(Py_hash_t) _Py_HashPointer(void*);
PyAPI_FUNC(Py_hash_t) _Py_HashBytes(unsigned char*, Py_ssize_t);
#endif #endif
/* Helper for passing objects to printf and the like */ /* Helper for passing objects to printf and the like */

View File

@ -283,6 +283,33 @@ class AbstractMemoryTests:
i = io.BytesIO(b'ZZZZ') i = io.BytesIO(b'ZZZZ')
self.assertRaises(TypeError, i.readinto, m) 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 # Variations on source objects for the buffer: bytes-like objects, then arrays
# with itemsize > 1. # with itemsize > 1.
# NOTE: support for multi-dimensional objects is unimplemented. # NOTE: support for multi-dimensional objects is unimplemented.

View File

@ -770,8 +770,8 @@ class SizeofTest(unittest.TestCase):
check(int(PyLong_BASE), size(vh) + 2*self.longdigit) 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-1), size(vh) + 2*self.longdigit)
check(int(PyLong_BASE**2), size(vh) + 3*self.longdigit) check(int(PyLong_BASE**2), size(vh) + 3*self.longdigit)
# memory # memory (Py_buffer + hash value)
check(memoryview(b''), size(h + 'PP2P2i7P')) check(memoryview(b''), size(h + 'PP2P2i7P' + 'P'))
# module # module
check(unittest, size(h + '3P')) check(unittest, size(h + '3P'))
# None # None

View File

@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
Core and Builtins 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 - Issue #13338: Handle all enumerations in _Py_ANNOTATE_MEMORY_ORDER
to allow compiling extension modules with -Wswitch-enum on gcc. to allow compiling extension modules with -Wswitch-enum on gcc.
Initial patch by Floris Bruynooghe. Initial patch by Floris Bruynooghe.

View File

@ -860,22 +860,11 @@ bytes_richcompare(PyBytesObject *a, PyBytesObject *b, int op)
static Py_hash_t static Py_hash_t
bytes_hash(PyBytesObject *a) bytes_hash(PyBytesObject *a)
{ {
register Py_ssize_t len; if (a->ob_shash == -1) {
register unsigned char *p; /* Can't fail */
register Py_uhash_t x; a->ob_shash = _Py_HashBytes((unsigned char *) a->ob_sval, Py_SIZE(a));
}
if (a->ob_shash != -1)
return a->ob_shash; 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;
} }
static PyObject* static PyObject*

View File

@ -84,6 +84,7 @@ PyMemoryView_FromBuffer(Py_buffer *info)
PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type); PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type);
if (mview == NULL) if (mview == NULL)
return NULL; return NULL;
mview->hash = -1;
dup_buffer(&mview->view, info); dup_buffer(&mview->view, info);
/* NOTE: mview->view.obj should already have been incref'ed as /* NOTE: mview->view.obj should already have been incref'ed as
part of PyBuffer_FillInfo(). */ part of PyBuffer_FillInfo(). */
@ -512,6 +513,37 @@ memory_repr(PyMemoryViewObject *self)
return PyUnicode_FromFormat("<memory at %p>", 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 */ /* Sequence methods */
static Py_ssize_t static Py_ssize_t
memory_length(PyMemoryViewObject *self) memory_length(PyMemoryViewObject *self)
@ -829,7 +861,7 @@ PyTypeObject PyMemoryView_Type = {
0, /* tp_as_number */ 0, /* tp_as_number */
&memory_as_sequence, /* tp_as_sequence */ &memory_as_sequence, /* tp_as_sequence */
&memory_as_mapping, /* tp_as_mapping */ &memory_as_mapping, /* tp_as_mapping */
0, /* tp_hash */ (hashfunc)memory_hash, /* tp_hash */
0, /* tp_call */ 0, /* tp_call */
0, /* tp_str */ 0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericGetAttr, /* tp_getattro */

View File

@ -743,6 +743,21 @@ _Py_HashPointer(void *p)
return x; 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 Py_hash_t
PyObject_HashNotImplemented(PyObject *v) PyObject_HashNotImplemented(PyObject *v)
{ {