From 9395c2d297fd4334e55ef2218633c90afb66d1a7 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 26 Jun 2017 23:57:51 +0100 Subject: [PATCH 01/17] Add support for np.bool_ in type_caster --- include/pybind11/cast.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index f3a05f00b4..cbac9a04f6 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1049,11 +1049,18 @@ template <> class type_caster : public void_caster class type_caster { public: - bool load(handle src, bool) { + bool load(handle src, bool convert) { if (!src) return false; else if (src.ptr() == Py_True) { value = true; return true; } else if (src.ptr() == Py_False) { value = false; return true; } - else return false; + else if (convert && hasattr(src, "dtype")) { + auto dtype = src.attr("dtype"); + if (hasattr(dtype, "kind") && dtype.attr("kind").cast() == 'b') { + value = PyObject_IsTrue(src.ptr()) == 1; + return true; + } + } + return false; } static handle cast(bool src, return_value_policy /* policy */, handle /* parent */) { return handle(src ? Py_True : Py_False).inc_ref(); From 4c4edce4af94488544ecdb90c8e359b7c3baaeb4 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 26 Jun 2017 23:58:29 +0100 Subject: [PATCH 02/17] Add a test for np.bool_ --- tests/test_numpy_dtypes.cpp | 1 + tests/test_numpy_dtypes.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 5f987a8471..07fc78b003 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -467,6 +467,7 @@ test_initializer numpy_dtypes([](py::module &m) { m.def("f_packed", [](PackedStruct s) { return s.uint_ * 10; }); m.def("f_nested", [](NestedStruct s) { return s.a.uint_ * 10; }); m.def("register_dtype", []() { PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_); }); + m.def("negate_bool", [](bool arg) { return !arg; }); }); #undef PYBIND11_PACKED diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 24803a97f6..2523543f80 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -319,3 +319,10 @@ def test_register_dtype(): def test_compare_buffer_info(): from pybind11_tests import compare_buffer_info assert all(compare_buffer_info()) + + +@pytest.requires_numpy +def test_numpy_bool(): + from pybind11_tests import negate_bool + assert negate_bool(np.bool_(True)) is False + assert negate_bool(np.bool_(False)) is True From 36b4b68903712a5feaf1bc065839f571b4d21654 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Tue, 27 Jun 2017 17:29:19 +0100 Subject: [PATCH 03/17] Support generic implicit conversion to bool --- include/pybind11/cast.h | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index cbac9a04f6..b07ed562f9 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1053,7 +1053,34 @@ template <> class type_caster { if (!src) return false; else if (src.ptr() == Py_True) { value = true; return true; } else if (src.ptr() == Py_False) { value = false; return true; } - else if (convert && hasattr(src, "dtype")) { + else if (convert) { + // This is quite similar to PyObject_IsTrue(), but it doesn't default + // to "True" for arbitrary objects. + Py_ssize_t res = -1; + auto tp = src.ptr()->ob_type; + if (src.is_none()) { + res = 0; + } + #if PY_MAJOR_VERSION >= 3 + else if (tp->tp_as_number && tp->tp_as_number->nb_bool) { + res = (*tp->tp_as_number->nb_bool)(src.ptr()); + } + #else + else if (tp->tp_as_number && tp->tp_as_number->nb_nonzero) { + res = (*tp->tp_as_number->nb_nonzero)(src.ptr()); + } + #endif + else if (tp->tp_as_mapping && tp->tp_as_mapping->mp_length) { + res = (*tp->tp_as_mapping->mp_length)(src.ptr()); + } else if (tp->tp_as_sequence && tp->tp_as_sequence->sq_length) { + res = (*tp->tp_as_sequence->sq_length)(src.ptr()); + } + if (res >= 0) { + value = res != 0; + return true; + } + } else if (hasattr(src, "dtype")) { + // Allow non-implicit conversion for numpy booleans auto dtype = src.attr("dtype"); if (hasattr(dtype, "kind") && dtype.attr("kind").cast() == 'b') { value = PyObject_IsTrue(src.ptr()) == 1; From 017e2f34d8e9337f76d37f1157a8ba0ea28a53ac Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Tue, 27 Jun 2017 17:29:46 +0100 Subject: [PATCH 04/17] Add tests for generic bool conversions --- tests/test_numpy_dtypes.cpp | 3 ++- tests/test_numpy_dtypes.py | 51 ++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 07fc78b003..3ca7696c42 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -467,7 +467,8 @@ test_initializer numpy_dtypes([](py::module &m) { m.def("f_packed", [](PackedStruct s) { return s.uint_ * 10; }); m.def("f_nested", [](NestedStruct s) { return s.a.uint_ * 10; }); m.def("register_dtype", []() { PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_); }); - m.def("negate_bool", [](bool arg) { return !arg; }); + m.def("bool_passthrough", [](bool arg) { return arg; }); + m.def("bool_passthrough_noconvert", [](bool arg) { return arg; }, py::arg().noconvert()); }); #undef PYBIND11_PACKED diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 2523543f80..77421a4149 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -323,6 +323,51 @@ def test_compare_buffer_info(): @pytest.requires_numpy def test_numpy_bool(): - from pybind11_tests import negate_bool - assert negate_bool(np.bool_(True)) is False - assert negate_bool(np.bool_(False)) is True + from pybind11_tests import bool_passthrough as convert, bool_passthrough_noconvert as noconvert + + require_implicit = lambda v: pytest.raises(TypeError, noconvert, v) + cant_convert = lambda v: pytest.raises(TypeError, convert, v) + + # straight up bool + assert convert(True) is True + assert convert(False) is False + assert noconvert(True) is True + assert noconvert(False) is False + + # np.bool_ is not considered implicit + assert convert(np.bool_(True)) is True + assert convert(np.bool_(False)) is False + assert noconvert(np.bool_(True)) is True + assert noconvert(np.bool_(False)) is False + + # None requires implicit conversion + require_implicit(None) + assert convert(None) is False + + # Sequence types check for lengths (same as in PyObject_IsTrue) + require_implicit([]) + require_implicit(()) + require_implicit('') + assert convert([]) is False + assert convert([1]) is True + assert convert(()) is False + assert convert((0,)) is True + assert convert('') is False + assert convert('foo') is True + + class A(object): + def __init__(self, x): self.x = x + def __nonzero__(self): return self.x + __bool__ = __nonzero__ + + class B(object): + pass + + # Arbitrary objects are not accepted + cant_convert(object()) + cant_convert(B()) + + # Objects with __nonzero__ / __bool__ defined can be converted + require_implicit(A(True)) + assert convert(A(True)) is True + assert convert(A(False)) is False From a5cec6338e47ae3ceaefb25acfb105d60b006570 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 28 Jun 2017 09:35:49 +0100 Subject: [PATCH 05/17] (Make flake8 happy) --- tests/test_numpy_dtypes.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 77421a4149..00ab5c816a 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -323,10 +323,14 @@ def test_compare_buffer_info(): @pytest.requires_numpy def test_numpy_bool(): - from pybind11_tests import bool_passthrough as convert, bool_passthrough_noconvert as noconvert + from pybind11_tests import (bool_passthrough as convert, + bool_passthrough_noconvert as noconvert) - require_implicit = lambda v: pytest.raises(TypeError, noconvert, v) - cant_convert = lambda v: pytest.raises(TypeError, convert, v) + def require_implicit(v): + pytest.raises(TypeError, noconvert, v) + + def cant_convert(v): + pytest.raises(TypeError, convert, v) # straight up bool assert convert(True) is True @@ -356,9 +360,14 @@ def test_numpy_bool(): assert convert('foo') is True class A(object): - def __init__(self, x): self.x = x - def __nonzero__(self): return self.x - __bool__ = __nonzero__ + def __init__(self, x): + self.x = x + + def __nonzero__(self): + return self.x + + def __bool__(self): + return self.x class B(object): pass From 839cc2d89366ce19c9a18c1e0aaf7308f42b029a Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 28 Jun 2017 10:06:55 +0100 Subject: [PATCH 06/17] Add a few more bool conversion tests --- tests/test_numpy_dtypes.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 00ab5c816a..428d00d586 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -352,12 +352,15 @@ def cant_convert(v): require_implicit([]) require_implicit(()) require_implicit('') + require_implicit({}) assert convert([]) is False assert convert([1]) is True assert convert(()) is False assert convert((0,)) is True assert convert('') is False assert convert('foo') is True + assert convert({}) is False + assert convert({1: 2}) is True class A(object): def __init__(self, x): @@ -372,6 +375,16 @@ def __bool__(self): class B(object): pass + class C(object): + def __init__(self, x): + self.x = x + + def __getitem__(self, _): + pass + + def __len__(self): + return self.x + # Arbitrary objects are not accepted cant_convert(object()) cant_convert(B()) @@ -380,3 +393,8 @@ class B(object): require_implicit(A(True)) assert convert(A(True)) is True assert convert(A(False)) is False + + # Objects implementing mapping or sequence protocol can be converted + require_implicit(C(1)) + assert convert(C(1)) is True + assert convert(C(0)) is False From 5de232f5e66c041da6324bbe5d15fef6250c96a5 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 09:11:24 +0100 Subject: [PATCH 07/17] Add PYBIND11_NONZERO constant (__bool__ magic) --- include/pybind11/common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/pybind11/common.h b/include/pybind11/common.h index ed1bb610bc..6e612cf790 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -152,8 +152,10 @@ #define PYBIND11_SLICE_OBJECT PyObject #define PYBIND11_FROM_STRING PyUnicode_FromString #define PYBIND11_STR_TYPE ::pybind11::str +#define PYBIND11_NONZERO "__bool__" #define PYBIND11_PLUGIN_IMPL(name) \ extern "C" PYBIND11_EXPORT PyObject *PyInit_##name() + #else #define PYBIND11_INSTANCE_METHOD_NEW(ptr, class_) PyMethod_New(ptr, nullptr, class_) #define PYBIND11_INSTANCE_METHOD_CHECK PyMethod_Check @@ -171,6 +173,7 @@ #define PYBIND11_SLICE_OBJECT PySliceObject #define PYBIND11_FROM_STRING PyString_FromString #define PYBIND11_STR_TYPE ::pybind11::bytes +#define PYBIND11_NONZERO "__nonzero__" #define PYBIND11_PLUGIN_IMPL(name) \ static PyObject *pybind11_init_wrapper(); \ extern "C" PYBIND11_EXPORT void init##name() { \ From 718641dfeb396b5196e5b83c8034081c0f4eba89 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 09:11:45 +0100 Subject: [PATCH 08/17] Bool conversion: run np.bool_ in the second pass --- include/pybind11/cast.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b07ed562f9..3d34c42922 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1079,7 +1079,9 @@ template <> class type_caster { value = res != 0; return true; } - } else if (hasattr(src, "dtype")) { + return false; + } + if (hasattr(src, "dtype")) { // Allow non-implicit conversion for numpy booleans auto dtype = src.attr("dtype"); if (hasattr(dtype, "kind") && dtype.attr("kind").cast() == 'b') { From 8ca10f3f1e0f864a8b5ef7fa918a8f3cec631ce4 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 09:13:27 +0100 Subject: [PATCH 09/17] Bool conversion: ignore sequences and mappings --- include/pybind11/cast.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3d34c42922..5e00af2f22 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1070,11 +1070,6 @@ template <> class type_caster { res = (*tp->tp_as_number->nb_nonzero)(src.ptr()); } #endif - else if (tp->tp_as_mapping && tp->tp_as_mapping->mp_length) { - res = (*tp->tp_as_mapping->mp_length)(src.ptr()); - } else if (tp->tp_as_sequence && tp->tp_as_sequence->sq_length) { - res = (*tp->tp_as_sequence->sq_length)(src.ptr()); - } if (res >= 0) { value = res != 0; return true; From 2f811f17c87b6fab5be11198039e6343b32f8d92 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 09:13:41 +0100 Subject: [PATCH 10/17] Bool conversion: handle PyPy --- include/pybind11/cast.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 5e00af2f22..5199a297ce 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1061,6 +1061,7 @@ template <> class type_caster { if (src.is_none()) { res = 0; } + #if !defined(PYPY_VERSION) #if PY_MAJOR_VERSION >= 3 else if (tp->tp_as_number && tp->tp_as_number->nb_bool) { res = (*tp->tp_as_number->nb_bool)(src.ptr()); @@ -1070,6 +1071,11 @@ template <> class type_caster { res = (*tp->tp_as_number->nb_nonzero)(src.ptr()); } #endif + #else + if (hasattr(src, PYBIND11_NONZERO)) { + res = PyObject_IsTrue(src.ptr()); + } + #endif if (res >= 0) { value = res != 0; return true; From a5ddfbb071745ebdd0f8468c2591fbb4962ecea8 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 09:22:54 +0100 Subject: [PATCH 11/17] (Update the tests) --- tests/test_numpy_dtypes.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 428d00d586..47d62dc98e 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -348,20 +348,6 @@ def cant_convert(v): require_implicit(None) assert convert(None) is False - # Sequence types check for lengths (same as in PyObject_IsTrue) - require_implicit([]) - require_implicit(()) - require_implicit('') - require_implicit({}) - assert convert([]) is False - assert convert([1]) is True - assert convert(()) is False - assert convert((0,)) is True - assert convert('') is False - assert convert('foo') is True - assert convert({}) is False - assert convert({1: 2}) is True - class A(object): def __init__(self, x): self.x = x From 2d4182ce2498b56fe995bd0512123f6d8104e1ee Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 09:46:26 +0100 Subject: [PATCH 12/17] (Update more tests) --- tests/test_numpy_dtypes.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 47d62dc98e..dad16810c4 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -361,16 +361,6 @@ def __bool__(self): class B(object): pass - class C(object): - def __init__(self, x): - self.x = x - - def __getitem__(self, _): - pass - - def __len__(self): - return self.x - # Arbitrary objects are not accepted cant_convert(object()) cant_convert(B()) @@ -379,8 +369,3 @@ def __len__(self): require_implicit(A(True)) assert convert(A(True)) is True assert convert(A(False)) is False - - # Objects implementing mapping or sequence protocol can be converted - require_implicit(C(1)) - assert convert(C(1)) is True - assert convert(C(0)) is False From 09d99db743cf9711ac2eda79741190d2d07d743c Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 10:04:50 +0100 Subject: [PATCH 13/17] Move bool caster tests to where they belong --- tests/test_builtin_casters.cpp | 4 +++ tests/test_builtin_casters.py | 55 ++++++++++++++++++++++++++++++++++ tests/test_numpy_dtypes.cpp | 2 -- tests/test_numpy_dtypes.py | 50 ------------------------------- 4 files changed, 59 insertions(+), 52 deletions(-) diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index bf972e19ec..b73e96ea57 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -116,6 +116,10 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("load_nullptr_t", [](std::nullptr_t) {}); // not useful, but it should still compile m.def("cast_nullptr_t", []() { return std::nullptr_t{}; }); + // test_bool_caster + m.def("bool_passthrough", [](bool arg) { return arg; }); + m.def("bool_passthrough_noconvert", [](bool arg) { return arg; }, py::arg().noconvert()); + // test_reference_wrapper m.def("refwrap_builtin", [](std::reference_wrapper p) { return 10 * p.get(); }); m.def("refwrap_usertype", [](std::reference_wrapper p) { return p.get().value(); }); diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index d7d49b6994..64d2f19456 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -265,3 +265,58 @@ def test_complex_cast(): """std::complex casts""" assert m.complex_cast(1) == "1.0" assert m.complex_cast(2j) == "(0.0, 2.0)" + + +def test_bool_caster(): + """Test bool caster implicit conversions.""" + convert, noconvert = m.bool_passthrough, m.bool_passthrough_noconvert + + def require_implicit(v): + pytest.raises(TypeError, noconvert, v) + + def cant_convert(v): + pytest.raises(TypeError, convert, v) + + # straight up bool + assert convert(True) is True + assert convert(False) is False + assert noconvert(True) is True + assert noconvert(False) is False + + # None requires implicit conversion + require_implicit(None) + assert convert(None) is False + + class A(object): + def __init__(self, x): + self.x = x + + def __nonzero__(self): + return self.x + + def __bool__(self): + return self.x + + class B(object): + pass + + # Arbitrary objects are not accepted + cant_convert(object()) + cant_convert(B()) + + # Objects with __nonzero__ / __bool__ defined can be converted + require_implicit(A(True)) + assert convert(A(True)) is True + assert convert(A(False)) is False + + +@pytest.requires_numpy +def test_numpy_bool(): + import numpy as np + convert, noconvert = m.bool_passthrough, m.bool_passthrough_noconvert + + # np.bool_ is not considered implicit + assert convert(np.bool_(True)) is True + assert convert(np.bool_(False)) is False + assert noconvert(np.bool_(True)) is True + assert noconvert(np.bool_(False)) is False diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 3ca7696c42..5f987a8471 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -467,8 +467,6 @@ test_initializer numpy_dtypes([](py::module &m) { m.def("f_packed", [](PackedStruct s) { return s.uint_ * 10; }); m.def("f_nested", [](NestedStruct s) { return s.a.uint_ * 10; }); m.def("register_dtype", []() { PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_); }); - m.def("bool_passthrough", [](bool arg) { return arg; }); - m.def("bool_passthrough_noconvert", [](bool arg) { return arg; }, py::arg().noconvert()); }); #undef PYBIND11_PACKED diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index dad16810c4..24803a97f6 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -319,53 +319,3 @@ def test_register_dtype(): def test_compare_buffer_info(): from pybind11_tests import compare_buffer_info assert all(compare_buffer_info()) - - -@pytest.requires_numpy -def test_numpy_bool(): - from pybind11_tests import (bool_passthrough as convert, - bool_passthrough_noconvert as noconvert) - - def require_implicit(v): - pytest.raises(TypeError, noconvert, v) - - def cant_convert(v): - pytest.raises(TypeError, convert, v) - - # straight up bool - assert convert(True) is True - assert convert(False) is False - assert noconvert(True) is True - assert noconvert(False) is False - - # np.bool_ is not considered implicit - assert convert(np.bool_(True)) is True - assert convert(np.bool_(False)) is False - assert noconvert(np.bool_(True)) is True - assert noconvert(np.bool_(False)) is False - - # None requires implicit conversion - require_implicit(None) - assert convert(None) is False - - class A(object): - def __init__(self, x): - self.x = x - - def __nonzero__(self): - return self.x - - def __bool__(self): - return self.x - - class B(object): - pass - - # Arbitrary objects are not accepted - cant_convert(object()) - cant_convert(B()) - - # Objects with __nonzero__ / __bool__ defined can be converted - require_implicit(A(True)) - assert convert(A(True)) is True - assert convert(A(False)) is False From 15c300059252918a034d58817f626b9bafc551bc Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 10:33:59 +0100 Subject: [PATCH 14/17] (Fix a compilation warning on PyPy) --- include/pybind11/cast.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 5199a297ce..02a27f3536 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1057,18 +1057,17 @@ template <> class type_caster { // This is quite similar to PyObject_IsTrue(), but it doesn't default // to "True" for arbitrary objects. Py_ssize_t res = -1; - auto tp = src.ptr()->ob_type; if (src.is_none()) { res = 0; } #if !defined(PYPY_VERSION) #if PY_MAJOR_VERSION >= 3 - else if (tp->tp_as_number && tp->tp_as_number->nb_bool) { - res = (*tp->tp_as_number->nb_bool)(src.ptr()); + else if (src.ptr()->ob_type->tp_as_number && src.ptr()->ob_type->tp_as_number->nb_bool) { + res = (*src.ptr()->ob_type->tp_as_number->nb_bool)(src.ptr()); } #else - else if (tp->tp_as_number && tp->tp_as_number->nb_nonzero) { - res = (*tp->tp_as_number->nb_nonzero)(src.ptr()); + else if (src.ptr()->ob_type->tp_as_number && src.ptr()->ob_type->tp_as_number->nb_nonzero) { + res = (*src.ptr()->ob_type->tp_as_number->nb_nonzero)(src.ptr()); } #endif #else From 9b4f56933732840be3977ebd72165e6743c95af2 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 17:38:45 +0100 Subject: [PATCH 15/17] Bool conversion: simplifications and comments --- include/pybind11/cast.h | 29 +++++++++++++---------------- include/pybind11/common.h | 6 ++++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 02a27f3536..41fcec02e5 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1054,29 +1054,26 @@ template <> class type_caster { else if (src.ptr() == Py_True) { value = true; return true; } else if (src.ptr() == Py_False) { value = false; return true; } else if (convert) { - // This is quite similar to PyObject_IsTrue(), but it doesn't default - // to "True" for arbitrary objects. Py_ssize_t res = -1; if (src.is_none()) { - res = 0; + res = 0; // None is implicitly converted to False } - #if !defined(PYPY_VERSION) - #if PY_MAJOR_VERSION >= 3 - else if (src.ptr()->ob_type->tp_as_number && src.ptr()->ob_type->tp_as_number->nb_bool) { - res = (*src.ptr()->ob_type->tp_as_number->nb_bool)(src.ptr()); - } - #else - else if (src.ptr()->ob_type->tp_as_number && src.ptr()->ob_type->tp_as_number->nb_nonzero) { - res = (*src.ptr()->ob_type->tp_as_number->nb_nonzero)(src.ptr()); + #if defined(PYPY_VERSION) + // On PyPy, check that "__bool__" (or "__nonzero__" on Python 2.7) attr exists + else if (hasattr(src, PYBIND11_BOOL_ATTR)) { + res = PyObject_IsTrue(src.ptr()); } - #endif #else - if (hasattr(src, PYBIND11_NONZERO)) { - res = PyObject_IsTrue(src.ptr()); + // Alternate approach for CPython: this does the same as the above, but optimized + // using the CPython API so as to avoid an unneeded attribute lookup. + else if (auto tp_as_number = src.ptr()->ob_type->tp_as_number) { + if (PYBIND11_NB_BOOL(tp_as_number)) { + res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); + } } #endif - if (res >= 0) { - value = res != 0; + if (res == 0 || res == 1) { + value = (bool) res; return true; } return false; diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 6e612cf790..b205bb9d80 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -152,7 +152,8 @@ #define PYBIND11_SLICE_OBJECT PyObject #define PYBIND11_FROM_STRING PyUnicode_FromString #define PYBIND11_STR_TYPE ::pybind11::str -#define PYBIND11_NONZERO "__bool__" +#define PYBIND11_BOOL_ATTR "__bool__" +#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_bool) #define PYBIND11_PLUGIN_IMPL(name) \ extern "C" PYBIND11_EXPORT PyObject *PyInit_##name() @@ -173,7 +174,8 @@ #define PYBIND11_SLICE_OBJECT PySliceObject #define PYBIND11_FROM_STRING PyString_FromString #define PYBIND11_STR_TYPE ::pybind11::bytes -#define PYBIND11_NONZERO "__nonzero__" +#define PYBIND11_BOOL_ATTR "__nonzero__" +#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_nonzero) #define PYBIND11_PLUGIN_IMPL(name) \ static PyObject *pybind11_init_wrapper(); \ extern "C" PYBIND11_EXPORT void init##name() { \ From 31401f816892ca73e163daf19fbdb4b6db0a23b7 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 10 Jul 2017 17:54:20 +0100 Subject: [PATCH 16/17] Bool conversion: only special-case np.bool_ in 1st pass --- include/pybind11/cast.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 41fcec02e5..3535cf9332 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1078,8 +1078,11 @@ template <> class type_caster { } return false; } - if (hasattr(src, "dtype")) { + else if (hasattr(src, "dtype")) { // Allow non-implicit conversion for numpy booleans + // + // Note: this will only run in the first (noconvert) pass; + // during the second pass, it will be handled by __bool__ logic. auto dtype = src.attr("dtype"); if (hasattr(dtype, "kind") && dtype.attr("kind").cast() == 'b') { value = PyObject_IsTrue(src.ptr()) == 1; From 025ba8a8b50cc25696ccb97aaa1834dd70b84aa0 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Sun, 23 Jul 2017 12:15:06 +0100 Subject: [PATCH 17/17] Simplify numpy.bool_ detection (use type name only) --- include/pybind11/cast.h | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3535cf9332..0b41f942bc 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1053,7 +1053,9 @@ template <> class type_caster { if (!src) return false; else if (src.ptr() == Py_True) { value = true; return true; } else if (src.ptr() == Py_False) { value = false; return true; } - else if (convert) { + else if (convert || !strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name)) { + // (allow non-implicit conversion for numpy booleans) + Py_ssize_t res = -1; if (src.is_none()) { res = 0; // None is implicitly converted to False @@ -1076,18 +1078,6 @@ template <> class type_caster { value = (bool) res; return true; } - return false; - } - else if (hasattr(src, "dtype")) { - // Allow non-implicit conversion for numpy booleans - // - // Note: this will only run in the first (noconvert) pass; - // during the second pass, it will be handled by __bool__ logic. - auto dtype = src.attr("dtype"); - if (hasattr(dtype, "kind") && dtype.attr("kind").cast() == 'b') { - value = PyObject_IsTrue(src.ptr()) == 1; - return true; - } } return false; }