【问题标题】:Why are (some) dict views hashable?为什么(某些)dict 视图是可散列的?
【发布时间】:2014-08-13 18:48:45
【问题描述】:

在 python 3 中,keys()values()items() 方法提供了它们各自元素的 dynamic views。这些被反向移植到 python 2.7 并且在那里以viewkeysviewvaluesviewitems 的形式提供。我在这里可以互换地指代它们。

对此有什么合理的解释:

#!/usr/bin/python3.4
In [1]: hash({}.keys())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-3727b260127e> in <module>()
----> 1 hash({}.keys())

TypeError: unhashable type: 'dict_keys'

In [2]: hash({}.items())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-decac720f012> in <module>()
----> 1 hash({}.items())

TypeError: unhashable type: 'dict_items'

In [3]: hash({}.values())
Out[3]: -9223363248553358775

我觉得这很令人惊讶。


python 文档glossary on "hashable" 说:

一个对象是可哈希的,如果它有一个永远不会改变的哈希值 在其生命周期内(它需要一个__hash__() 方法),并且可以 与其他对象相比(它需要一个__eq__() 方法)。可散列 比较相等的对象必须具有相同的哈希值。

好的,第一部分实际检查出来了; dict_values 对象的哈希值似乎不会在其生命周期内发生变化——即使它的基础值肯定会发生变化。

In [11]: d = {}

In [12]: vals = d.values()

In [13]: vals.__hash__()
Out[13]: -9223363248553358718

In [14]: d['a'] = 'b'

In [15]: vals
Out[15]: dict_values(['b'])

In [16]: vals.__hash__()
Out[16]: -9223363248553358718

但是关于__eq__()...的部分实际上并没有。

In [17]: {'a':'a'}.values().__eq__('something else')
Out[17]: NotImplemented

所以...是的。有人能理解这一点吗?三个viewfoo 方法中只有dict_values 对象是可散列的,是否存在这种不对称的原因?

【问题讨论】:

标签: python


【解决方案1】:

我相信这是因为viewitemsviewkeys 提供了自定义丰富的比较功能,但viewvalues 没有。 Here是每种视图类型的定义:

PyTypeObject PyDictKeys_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict_keys",                                /* tp_name */
    sizeof(dictviewobject),                     /* tp_basicsize */
    0,                                          /* tp_itemsize */
    /* methods */
    (destructor)dictview_dealloc,               /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc)dictview_repr,                    /* tp_repr */
    &dictviews_as_number,                       /* tp_as_number */
    &dictkeys_as_sequence,                      /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
    0,                                          /* tp_doc */
    (traverseproc)dictview_traverse,            /* tp_traverse */
    0,                                          /* tp_clear */
    dictview_richcompare,                       /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    (getiterfunc)dictkeys_iter,                 /* tp_iter */
    0,                                          /* tp_iternext */
    dictkeys_methods,                           /* tp_methods */
    0,
};

PyTypeObject PyDictItems_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict_items",                               /* tp_name */
    sizeof(dictviewobject),                     /* tp_basicsize */
    0,                                          /* tp_itemsize */
    /* methods */
    (destructor)dictview_dealloc,               /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc)dictview_repr,                    /* tp_repr */
    &dictviews_as_number,                       /* tp_as_number */
    &dictitems_as_sequence,                     /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
    0,                                          /* tp_doc */
    (traverseproc)dictview_traverse,            /* tp_traverse */
    0,                                          /* tp_clear */
    dictview_richcompare,                       /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    (getiterfunc)dictitems_iter,                /* tp_iter */
    0,                                          /* tp_iternext */
    dictitems_methods,                          /* tp_methods */
    0,
};

PyTypeObject PyDictValues_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dict_values",                              /* tp_name */
    sizeof(dictviewobject),                     /* tp_basicsize */
    0,                                          /* tp_itemsize */
    /* methods */
    (destructor)dictview_dealloc,               /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (reprfunc)dictview_repr,                    /* tp_repr */
    0,                                          /* tp_as_number */
    &dictvalues_as_sequence,                    /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    PyObject_GenericGetAttr,                    /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
    0,                                          /* tp_doc */
    (traverseproc)dictview_traverse,            /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    (getiterfunc)dictvalues_iter,               /* tp_iter */
    0,                                          /* tp_iternext */
    dictvalues_methods,                         /* tp_methods */
    0,
};

请注意,tp_richcompare 定义为 dictview_richcompare 用于 itemskeys,但不是 values。现在,documentation for __hash__ 是这样说的:

覆盖__eq__() 且未定义__hash__() 的类将其__hash__() 隐式设置为None。

...

如果覆盖__eq__()的类需要保留实现 来自父类的__hash__(),必须告诉解释器 通过设置__hash__ = &lt;ParentClass&gt;.__hash__ 显式设置。

如果没有覆盖__eq__() 的类希望抑制散列 支持,它应该在类定义中包含__hash__ = None。`

所以,因为items/keys 覆盖__eq__()(通过提供tp_richcompare 函数),他们需要明确定义__hash__ 等于父级的,以保留其实现。因为values没有覆盖__eq__(),它从object继承了__hash__,因为tp_hashtp_richcompareget inherited from the parent if they're both NULL

该字段与 tp_richcompare 一起被子类型继承:a 子类型继承 tp_richcompare 和 tp_hash,当 子类型的 tp_richcompare 和 tp_hash 都是 NULL。

dict_values 的实现并没有阻止这种自动继承这一事实可能被认为是一个错误。

【讨论】:

  • 谢谢达诺。欣赏并同意您的发现。似乎tp_hash(对于所有三个,真的)应该明确设置为PyObject_HashNotImplemented 以便修复。我将在错误跟踪器上打开一个问题。
  • 看起来@roippi 为此提交了issue22192,对于任何感兴趣的人。
  • 五年后这个错误仍然存​​在(2019 年 9 月 15 日)。我最近自己发现了它,今天找到了这个问题和答案。我可以在我的代码中解决它,但很遗憾它在这么长时间后仍未修复。
猜你喜欢
  • 2010-12-29
  • 1970-01-01
  • 2023-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-27
相关资源
最近更新 更多