I found when converting some Python2/Qt4 code to Python3/Qt5, that apparently QStandardItem can no longer be used as a dict key in as it doesn't have __hash__
implemented, and therefore is not considered immutable anymore.
Those two snippets show the issue:
PyQt4:
>>> from PyQt4 import QtGui
>>> a = QtGui.QStandardItem()
>>> b = {}
>>> b[a] = "1"
>>> a.__hash__()
2100390
PyQt5:
>>> from PyQt5 import QtGui
>>> a = QtGui.QStandardItem()
>>> b = {}
>>> b[a] = "1"
TypeError: unhashable type: 'QStandardItem'
>>> a.__hash__()
TypeError: 'NoneType' object is not callable
Why was the change done? Should I not use QStandardItem as a dict key?
The obvious workaround would be to subclass QStandardItem and reimplement a trivial version of __hash__
(which I've done). But is there anything I'm missing?
In Qt, there are three requirements for hashability:
==
operatorqHash
function must be defined for the typeSo if PyQt wants to maintain consistency with Qt, it should only define __hash__
when the above conditions apply, and its implementation of it should simply delegate to whatever qHash
function Qt provides.
The behaviour when using Python 2 with PyQt4/5 should probably be considered a misfeature, because the results it gives are not consistent with Qt. This can be seen by looking at what happens with a type that is hashable (in Qt terms):
Using Python 3:
>>> a = QtCore.QUrl('foo.bar')
>>> b = QtCore.QUrl('foo.bar')
>>> a == b
True
>>> hash(a) == hash(b)
True
This is exactly what we want: objects that compare equal, should also hash equal. But now look at what happens when using the same version of PyQt with Python 2:
>>> a = Qt.QUrl('foo.bar')
>>> b = Qt.QUrl('foo.bar')
>>> a == b
True
>>> hash(a) == hash(b)
False
It seems that something like the object's identity is used by the __hash__
implementation in Python 2, which obviously won't ever be consistent with Qt's hashing semantics.
The QStandardItem
class has never been hashable in Qt, so for the sake of consistency, PyQt now chooses not to provide a __hash__
method for it. And since instances of QStandardItem
are, in fact, mutable, PyQt quite reasonably leaves it up to the user to decide when to define __hash__
, and how to implement it. For compatibility with Python 2, this could probably just return id(self)
.