In [30]: import numpy as np
In [31]: d = np.dtype(np.float64)
In [32]: d
Out[32]: dtype('float64')
In [33]: d == np.float64
Out[33]: True
In [34]: hash(np.float64)
Out[34]: -9223372036575774449
In [35]: hash(d)
Out[35]: 880835502155208439
Run Code Online (Sandbox Code Playgroud)
为什么这些dtypes比较相同但哈希不同?
请注意,Python确实承诺:
唯一需要的属性是比较相等的对象具有相同的哈希值...
我对此问题的解决方法是调用np.dtype
所有内容,之后哈希值和比较是一致的.
如前所述tttthomasssss
,和type
的(类)是不同的。它们是不同种类的东西:np.float64
d
In [435]: type(np.float64)
Out[435]: type
Run Code Online (Sandbox Code Playgroud)
类型type
(通常)意味着它是一个函数,因此它可以用作:
In [436]: np.float64(0)
Out[436]: 0.0
In [437]: type(_)
Out[437]: numpy.float64
Run Code Online (Sandbox Code Playgroud)
创建一个数字对象。实际上,这看起来更像是一个类定义。但由于numpy
使用了大量编译代码,并且它ndarray
使用自己的代码__new__
,因此如果它跨越界限,我不会感到惊讶。
In [438]: np.float64.__hash__??
Type: wrapper_descriptor
String Form:<slot wrapper '__hash__' of 'float' objects>
Docstring: x.__hash__() <==> hash(x)
Run Code Online (Sandbox Code Playgroud)
我以为这会是hash(np.float64)
,但它实际上可能是该类型的对象的哈希值,例如hash(np.float64(0))
。在这种情况下hash(np.float64)
只需使用默认type.__hash__
方法。
继续dtype
:
In [439]: d=np.dtype(np.float64)
In [440]: type(d)
Out[440]: numpy.dtype
Run Code Online (Sandbox Code Playgroud)
d
不是函数或类:
In [441]: d(0)
...
TypeError: 'numpy.dtype' object is not callable
In [442]: d.__hash__??
Type: method-wrapper
String Form:<method-wrapper '__hash__' of numpy.dtype object at 0xb60f8a60>
Docstring: x.__hash__() <==> hash(x)
Run Code Online (Sandbox Code Playgroud)
看起来np.dtype
没有定义任何特殊__hash__
方法,它只是继承自object
.
float64
进一步说明和的区别d
,看类继承栈
In [443]: np.float64.__mro__
Out[443]:
(numpy.float64,
numpy.floating,
numpy.inexact,
numpy.number,
numpy.generic,
float,
object)
In [444]: d.__mro__
...
AttributeError: 'numpy.dtype' object has no attribute '__mro__'
In [445]: np.dtype.__mro__
Out[445]: (numpy.dtype, object)
Run Code Online (Sandbox Code Playgroud)
所以np.float64
也没有定义哈希,它只是继承自float
. d
没有 an__mro__
因为它是一个对象,而不是一个类。
numpy
拥有足够的编译代码和悠久的历史,您不能指望 Python 文档始终适用。
np.dtype
显然np.float64
有__eq__
允许它们相互比较的方法,但numpy
开发人员没有付出任何努力来确保这些__hash__
方法符合要求。最有可能的是因为他们不需要使用任何一个作为字典键。
我从未见过这样的代码:
In [453]: dd={np.float64:12,d:34}
In [454]: dd
Out[454]: {dtype('float64'): 34, numpy.float64: 12}
In [455]: dd[np.float64]
Out[455]: 12
In [456]: dd[d]
Out[456]: 34
Run Code Online (Sandbox Code Playgroud)
它们不应该这样做,但是__eq__
对象__hash__
在numpy.dtype
本质上无法修复的设计级别上被破坏了。对于这个答案,我将大量引用 njsmith 对dtype 相关错误报告的评论。
np.float64
实际上不是 dtype。它是 Python 类型系统普通意义上的一种类型。具体来说,如果您从 float64 数据类型数组中检索标量,np.float64
则 是结果标量的类型。
np.dtype(np.float64)
是一个 dtype,一个 的实例numpy.dtype
。dtypes 是 NumPy 记录 NumPy 数组内容结构的方式。它们对于结构化数组尤其重要,因为结构化数组可能具有非常复杂的数据类型。虽然普通的 Python 类型可以填补数据类型的大部分角色,但为新的结构化数组动态创建新类型将非常尴尬,而且在类型类统一之前的日子里这可能是不可能的。
numpy.dtype
实现__eq__
基本上是这样的:
def __eq__(self, other):
if isinstance(other, numpy.dtype):
return regular_comparison(self, other)
return self == numpy.dtype(other)
Run Code Online (Sandbox Code Playgroud)
这很糟糕。除其他问题外,它不是传递性的,它TypeError
在应该返回时引发NotImplemented
,并且由于 dtype 强制的工作原理,它的输出有时确实很奇怪:
>>> x = numpy.dtype(numpy.float64)
>>> x == None
True
Run Code Online (Sandbox Code Playgroud)
numpy.dtype.__hash__
也好不到哪儿去。__hash__
它没有尝试与所有其他类型接受的方法保持一致numpy.dtype.__eq__
(并且要处理这么多不兼容的类型,怎么可能呢?)。哎呀,它甚至不应该存在,因为 dtype 对象是可变的!不仅仅是像模块或文件对象那样可变,因为它们可以__eq__
按__hash__
身份工作。dtype 对象是可变的,这实际上会改变它们的哈希值:
>>> x = numpy.dtype([('f1', float)])
>>> hash(x)
-405377605
>>> x.names = ['f2']
>>> hash(x)
1908240630
Run Code Online (Sandbox Code Playgroud)
当您尝试比较时d == np.float64
,d.__eq__
构建一个 dtypenp.float64
并发现它d == np.dtype(np.float64)
是 True。但是,当您获取它们的哈希值时,np.float64
请使用类型对象的常规(基于身份的)哈希值,并d
使用 dtype 对象的哈希值。通常,不同类型的相同对象应该具有相同的哈希值,但 dtype 实现并不关心这一点。
__eq__
不幸的是,在不__hash__
破坏人们所依赖的 API 的情况下解决 dtype 的问题是不可能的。人们依赖于诸如x.dtype == 'float64'
或 之类的东西x.dtype == np.float64
,而修复数据类型会打破这一点。