【问题标题】:numpy ndarray hashabilitynumpy ndarray 散列性
【发布时间】:2012-03-21 19:14:31
【问题描述】:

我在理解如何管理 numpy 对象的哈希性时遇到了一些问题。

>>> import numpy as np
>>> class Vector(np.ndarray):
...     pass
>>> nparray = np.array([0.])
>>> vector = Vector(shape=(1,), buffer=nparray)
>>> ndarray = np.ndarray(shape=(1,), buffer=nparray)
>>> nparray
array([ 0.])
>>> ndarray
array([ 0.])
>>> vector
Vector([ 0.])
>>> '__hash__' in dir(nparray)
True
>>> '__hash__' in dir(ndarray)
True
>>> '__hash__' in dir(vector)
True
>>> hash(nparray)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'numpy.ndarray'
>>> hash(ndarray)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'numpy.ndarray'
>>> hash(vector)
-9223372036586049780
>>> nparray.__hash__()
269709177
>>> ndarray.__hash__()
269702147
>>> vector.__hash__()
-9223372036586049780
>>> id(nparray)
4315346832
>>> id(ndarray)
4315234352
>>> id(vector)
4299616456
>>> nparray.__hash__() == id(nparray)
False
>>> ndarray.__hash__() == id(ndarray)
False
>>> vector.__hash__() == id(vector)
False
>>> hash(vector) == vector.__hash__()
True

怎么会

  • numpy 对象定义了一个 __hash__ 方法,但不可散列
  • 派生numpy.ndarray 的类定义__hash__ 并且可散列的吗?

我错过了什么吗?

我正在使用 Python 2.7.1 和 numpy 1.6.1

感谢您的帮助!

编辑:添加对象ids

编辑2: 根据 deinonychusaur 的评论并试图弄清楚散列是否基于内容,我玩了 numpy.nparray.dtype 并发现了一些我觉得很奇怪的东西:

>>> [Vector(shape=(1,), buffer=np.array([1], dtype=mytype), dtype=mytype) for mytype in ('float', 'int', 'float128')]
[Vector([ 1.]), Vector([1]), Vector([ 1.0], dtype=float128)]
>>> [id(Vector(shape=(1,), buffer=np.array([1], dtype=mytype), dtype=mytype)) for mytype in ('float', 'int', 'float128')]
[4317742576, 4317742576, 4317742576]
>>> [hash(Vector(shape=(1,), buffer=np.array([1], dtype=mytype), dtype=mytype)) for mytype in ('float', 'int', 'float128')]
[269858911, 269858911, 269858911]

我很困惑...... numpy 中有一些(类型无关的)缓存机制吗?

【问题讨论】:

  • 这似乎展示了如何让它工作,似乎处理了数组是可变的事实。 stackoverflow.com/a/5173201/1099682
  • 我知道可变对象不应该是可散列的。但是在这里,我的 Vectorclass 只是从 numpy.ndarray 派生而来,它是不可散列的,但 Vector 类是,即使它是可变的。
  • 在我看来,散列的是内存参考或其他东西,如果你只是重复 vector = Vector(shape=(1,), buffer=nparray) 并检查它应该有的散列改变了。

标签: python numpy


【解决方案1】:

我在 Python 2.6.6 和 numpy 1.3.0 中得到了相同的结果。根据the Python glossary,如果定义了__hash__(而不是None),并且定义了__eq____cmp__,则对象应该是可散列的。 ndarray.__eq__ndarray.__hash__ 都已定义并返回有意义的内容,所以我不明白为什么 hash 会失败。在快速谷歌之后,我找到了this post on the python.scientific.devel mailing list,它指出数组从来没有被设计为可散列的——所以为什么要定义ndarray.__hash__,我不知道。注意isinstance(nparray, collections.Hashable) 返回True

编辑:注意nparray.__hash__() 返回的结果与id(nparray) 相同,所以这只是默认实现。也许在早期版本的 python 中删除 __hash__ 的实现是困难或不可能的(__hash__ = None 技术显然是在 2.6 中引入的),所以他们使用某种 C API 魔法以一种不会的方式实现这一点t 传播到子类,并且不会阻止您显式调用ndarray.__hash__

Python 3.2.2 和来自 repo 的当前 numpy 2.0.0 中的情况有所不同。 __cmp__ 方法不再存在,因此哈希性现在需要__hash____eq__(请参阅Python 3 glossary)。在这个版本的numpy中,定义了ndarray.__hash__,但它只是None,所以不能调用。 hash(nparray) 失败,isinstance(nparray, collections.Hashable) 按预期返回 Falsehash(vector) 也失败了。

【讨论】:

  • 非常感谢您的回答。关于您的编辑,我不会复制您所说的话。我实际上有 >>> nparray.__hash__() 269709177 >>> id(nparray) 4315346832 所以我仍然很困惑。我在我的帖子中添加了这个,因为代码在 cmets 中不可读
  • 嗯...哈希值与我在 Python 2.6.6 和 numpy 1.3.0 中的 id 相同,但在 Python 2.7.2 和 numpy 1.5.1 中不同。这一切都很奇怪。如果我没有碰巧在我敲过的每个 Python 版本上都有不同版本的 numpy,这可能会有所帮助。无论如何,据我所知,__hash__ 的默认定义已经返回 id 很长一段时间了,所以我想他们必须明确地重写它以至少在某些版本的 numpy 中做一些不同的事情。跨度>
【解决方案2】:

这不是一个明确的答案,但这里有一些线索可以理解这种行为。

我这里指的是1.6.1版本的numpy代码。

根据numpy.ndarray对象实现(看,numpy/core/src/multiarray/arrayobject.c),hash方法设置为NULL

NPY_NO_EXPORT PyTypeObject PyArray_Type = {
#if defined(NPY_PY3K)
    PyVarObject_HEAD_INIT(NULL, 0)
#else
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
#endif
    "numpy.ndarray",                            /* tp_name */
    sizeof(PyArrayObject),                      /* tp_basicsize */
    &array_as_mapping,                          /* tp_as_mapping */
    (hashfunc)0,                                /* tp_hash */

这个tp_hash 属性似乎在numpy/core/src/multiarray/multiarraymodule.c 中被覆盖。请参阅 DUAL_INHERITDUAL_INHERIT2initmultiarray 函数,其中修改了 tp_hash 属性。

例如: PyArrayDescr_Type.tp_hash = PyArray_DescrHash

根据hashdescr.c,hash实现如下:

* How does this work ? The hash is computed from a list which contains all the
* information specific to a type. The hard work is to build the list
* (_array_descr_walk). The list is built as follows:
*      * If the dtype is builtin (no fields, no subarray), then the list
*      contains 6 items which uniquely define one dtype (_array_descr_builtin)
*      * If the dtype is a compound array, one walk on each field. For each
*      field, we append title, names, offset to the final list used for
*      hashing, and then append the list recursively built for each
*      corresponding dtype (_array_descr_walk_fields)
*      * If the dtype is a subarray, one adds the shape tuple to the list, and
*      then append the list recursively built for each corresponding type
*      (_array_descr_walk_subarray)

【讨论】:

    猜你喜欢
    • 2018-10-10
    • 2023-03-29
    • 2018-07-28
    • 2021-12-01
    • 2011-05-02
    • 2021-09-26
    • 2011-11-25
    • 2015-08-07
    相关资源
    最近更新 更多