为什么这些dtypes比较相同但哈希不同?

Nei*_*l G 8 python numpy

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所有内容,之后哈希值和比较是一致的.

hpa*_*ulj 5

如前所述tttthomasssss,和type的(类)是不同的。它们是不同种类的东西:np.float64d

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)


use*_*ica 5

它们不应该这样做,但是__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.float64d.__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,而修复数据类型会打破这一点。