Access C++ hash functions from Python and vice versa (#1034)

There are two separate additions:

1. `py::hash(obj)` is equivalent to the Python `hash(obj)`.
2. `.def(hash(py::self))` registers the hash function defined by
   `std::hash<T>` as the Python hash function.
This commit is contained in:
Bruce Merry 2017-08-30 14:22:00 +02:00 committed by Dean Moldovan
parent 29b99a11a4
commit 37de2da9dd
7 changed files with 44 additions and 1 deletions

View File

@ -243,6 +243,11 @@ v2.2.0 (Not yet released)
* Fixed implicit conversion of `py::enum_` to integer types on Python 2.7. * Fixed implicit conversion of `py::enum_` to integer types on Python 2.7.
`#821 <https://github.com/pybind/pybind11/pull/821>`_. `#821 <https://github.com/pybind/pybind11/pull/821>`_.
* Added ``py::hash`` to fetch the hash value of Python objects, and
``.def(hash(py::self))`` to provide the C++ ``std::hash`` as the Python
``__hash__`` method.
`#1034 <https://github.com/pybind/pybind11/pull/1034>`_.
* Fixed ``__truediv__`` on Python 2 and ``__itruediv__`` on Python 3. * Fixed ``__truediv__`` on Python 2 and ``__itruediv__`` on Python 3.
`#867 <https://github.com/pybind/pybind11/pull/867>`_. `#867 <https://github.com/pybind/pybind11/pull/867>`_.

View File

@ -28,7 +28,7 @@ enum op_id : int {
op_int, op_long, op_float, op_str, op_cmp, op_gt, op_ge, op_lt, op_le, op_int, op_long, op_float, op_str, op_cmp, op_gt, op_ge, op_lt, op_le,
op_eq, op_ne, op_iadd, op_isub, op_imul, op_idiv, op_imod, op_ilshift, op_eq, op_ne, op_iadd, op_isub, op_imul, op_idiv, op_imod, op_ilshift,
op_irshift, op_iand, op_ixor, op_ior, op_complex, op_bool, op_nonzero, op_irshift, op_iand, op_ixor, op_ior, op_complex, op_bool, op_nonzero,
op_repr, op_truediv, op_itruediv op_repr, op_truediv, op_itruediv, op_hash
}; };
enum op_type : int { enum op_type : int {
@ -148,6 +148,7 @@ PYBIND11_INPLACE_OPERATOR(ior, operator|=, l |= r)
PYBIND11_UNARY_OPERATOR(neg, operator-, -l) PYBIND11_UNARY_OPERATOR(neg, operator-, -l)
PYBIND11_UNARY_OPERATOR(pos, operator+, +l) PYBIND11_UNARY_OPERATOR(pos, operator+, +l)
PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l)) PYBIND11_UNARY_OPERATOR(abs, abs, std::abs(l))
PYBIND11_UNARY_OPERATOR(hash, hash, std::hash<L>()(l))
PYBIND11_UNARY_OPERATOR(invert, operator~, (~l)) PYBIND11_UNARY_OPERATOR(invert, operator~, (~l))
PYBIND11_UNARY_OPERATOR(bool, operator!, !!l) PYBIND11_UNARY_OPERATOR(bool, operator!, !!l)
PYBIND11_UNARY_OPERATOR(int, int_, (int) l) PYBIND11_UNARY_OPERATOR(int, int_, (int) l)

View File

@ -390,6 +390,13 @@ inline void setattr(handle obj, handle name, handle value) {
inline void setattr(handle obj, const char *name, handle value) { inline void setattr(handle obj, const char *name, handle value) {
if (PyObject_SetAttrString(obj.ptr(), name, value.ptr()) != 0) { throw error_already_set(); } if (PyObject_SetAttrString(obj.ptr(), name, value.ptr()) != 0) { throw error_already_set(); }
} }
inline ssize_t hash(handle obj) {
auto h = PyObject_Hash(obj.ptr());
if (h == -1) { throw error_already_set(); }
return h;
}
/// @} python_builtins /// @} python_builtins
NAMESPACE_BEGIN(detail) NAMESPACE_BEGIN(detail)

View File

@ -10,6 +10,7 @@
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include "constructor_stats.h" #include "constructor_stats.h"
#include <pybind11/operators.h> #include <pybind11/operators.h>
#include <functional>
class Vector2 { class Vector2 {
public: public:
@ -53,6 +54,14 @@ int operator+(const C2 &, const C2 &) { return 22; }
int operator+(const C2 &, const C1 &) { return 21; } int operator+(const C2 &, const C1 &) { return 21; }
int operator+(const C1 &, const C2 &) { return 12; } int operator+(const C1 &, const C2 &) { return 12; }
namespace std {
template<>
struct hash<Vector2> {
// Not a good hash function, but easy to test
size_t operator()(const Vector2 &) { return 4; }
};
}
TEST_SUBMODULE(operators, m) { TEST_SUBMODULE(operators, m) {
// test_operator_overloading // test_operator_overloading
@ -77,6 +86,7 @@ TEST_SUBMODULE(operators, m) {
.def(float() * py::self) .def(float() * py::self)
.def(float() / py::self) .def(float() / py::self)
.def("__str__", &Vector2::toString) .def("__str__", &Vector2::toString)
.def(hash(py::self))
; ;
m.attr("Vector") = m.attr("Vector2"); m.attr("Vector") = m.attr("Vector2");

View File

@ -35,6 +35,8 @@ def test_operator_overloading():
v2 /= v1 v2 /= v1
assert str(v2) == "[2.000000, 8.000000]" assert str(v2) == "[2.000000, 8.000000]"
assert hash(v1) == 4
cstats = ConstructorStats.get(m.Vector2) cstats = ConstructorStats.get(m.Vector2)
assert cstats.alive() == 2 assert cstats.alive() == 2
del v1 del v1

View File

@ -261,4 +261,6 @@ TEST_SUBMODULE(pytypes, m) {
}); });
m.def("print_failure", []() { py::print(42, UnregisteredType()); }); m.def("print_failure", []() { py::print(42, UnregisteredType()); });
m.def("hash_function", [](py::object obj) { return py::hash(obj); });
} }

View File

@ -220,3 +220,19 @@ def test_print(capture):
if debug_enabled else if debug_enabled else
"arguments to Python object (compile in debug mode for details)" "arguments to Python object (compile in debug mode for details)"
) )
def test_hash():
class Hashable(object):
def __init__(self, value):
self.value = value
def __hash__(self):
return self.value
class Unhashable(object):
__hash__ = None
assert m.hash_function(Hashable(42)) == 42
with pytest.raises(TypeError):
m.hash_function(Unhashable())