Skip to content

Overriding __eq__ does not set __hash__ to NoneΒ #2191

@ixje

Description

@ixje

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 checking isinstance(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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions