-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Issue description
The Python documentation states
A class that overrides
__eq__()and does not define__hash__()will have its__hash__()implicitly set to None. When the__hash__()method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable when checkingisinstance(obj, collections.abc.Hashable).
Thus if we override __eq__ but not __hash__ we should get an unhashable type (e.g. cannot be used in a set container). Pybind however seems to always have a __hash__ implemented, regardless if we override __eq__. The expected behaviour is that __hash__ is set to None
Reproducible example code
In Python it exhibits the following behaviour
from collections import abc
class MyClass1:
pass
class MyClass2:
def __eq__(self, other):
return True
def main():
c1 = MyClass1()
c2 = MyClass2()
print(isinstance(c1, abc.Hashable)) # True
print(isinstance(c2, abc.Hashable)) # False <- OK
if __name__ == "__main__":
main()Using pybind
#include <pybind11/pybind11.h>
namespace py = pybind11;
class DummyClass {};
class DummyClass2 {};
PYBIND11_MODULE(DummyClass, m) {
py::class_<DummyClass>(m, "MyClass1")
.def(py::init());
py::class_<DummyClass2>(m, "MyClass2")
.def(py::init())
.def("__eq__", [](py::object&) { return true; });
}from collections import abc
from lib.DummyClass import MyClass1, MyClass2
def main():
c1 = MyClass1()
c2 = MyClass2()
print(isinstance(c1, abc.Hashable)) # True
print(isinstance(c2, abc.Hashable)) # True <- NOT OK
if __name__ == "__main__":
main()The workaround
A workaround is to manually add .attr("__hash__") = py::none()
I'm assuming it should be possible to detect if there is a __eq__ override without any __hash__ override and then set the attribute. This would resolve the deviating/unexpected behaviour.