I believe this occurs because viewitems and viewkeys provide custom rich comparison functions, but viewvalues does not. Here are the definitions of each view type:
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,
};
Notice that tp_richcompare is defined as dictview_richcompare for items and keys, but not values. Now, the documentation for __hash__ says this:
A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None.
...
If a class that overrides __eq__() needs to retain the implementation
of __hash__() from a parent class, the interpreter must be told this
explicitly by setting __hash__ = <ParentClass>.__hash__.
If a class that does not override __eq__() wishes to suppress hash
support, it should include __hash__ = None in the class definition.`
So, because items/keys are overriding __eq__() (by providing a tp_richcompare function), they would need to explicitly define __hash__ as being equal to the parent's to retain an implementation for it. Because values doesn't override __eq__(), it inherits the __hash__ from object, because tp_hash and tp_richcompare get inherited from the parent if they're both NULL:
This field is inherited by subtypes together with tp_richcompare: a
subtype inherits both of tp_richcompare and tp_hash, when the
subtype’s tp_richcompare and tp_hash are both NULL.
The fact that the impelmentation for dict_values isn't preventing this automatic inheritence would probably be considered a bug.