Issue #13411: memoryview objects are now hashable when the underlying object is hashable.
This commit is contained in:
parent
0a3229de6b
commit
ce4a9da705
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue