From cf0d0f9d5ad79349e313694ddf3efeef7de21890 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 30 Nov 2017 11:33:24 -0600 Subject: [PATCH] Matching Python 2 int behavior on Python 2 (#1186) Pybind11's default conversion to int always produces a long on Python 2 (`int`s and `long`s were unified in Python 3). This patch fixes `int` handling to match Python 2 on Python 2; for short types (`size_t` or smaller), the number will be returned as an `int` if possible, otherwise `long`. Requires Python 2.5+. This is needed for things like `sys.exit`, which refuse to accept a `long`. --- include/pybind11/cast.h | 7 ++++--- include/pybind11/detail/common.h | 4 ++++ tests/test_builtin_casters.cpp | 5 +++++ tests/test_builtin_casters.py | 13 +++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 715ec932..b3ab7f23 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -980,11 +980,12 @@ public: static handle cast(T src, return_value_policy /* policy */, handle /* parent */) { if (std::is_floating_point::value) { return PyFloat_FromDouble((double) src); - } else if (sizeof(T) <= sizeof(long)) { + } else if (sizeof(T) <= sizeof(ssize_t)) { + // This returns a long automatically if needed if (std::is_signed::value) - return PyLong_FromLong((long) src); + return PYBIND11_LONG_FROM_SIGNED(src); else - return PyLong_FromUnsignedLong((unsigned long) src); + return PYBIND11_LONG_FROM_UNSIGNED(src); } else { if (std::is_signed::value) return PyLong_FromLongLong((long long) src); diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 7692063a..7d629c0f 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -158,6 +158,8 @@ #define PYBIND11_BYTES_SIZE PyBytes_Size #define PYBIND11_LONG_CHECK(o) PyLong_Check(o) #define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o) +#define PYBIND11_LONG_FROM_SIGNED(o) PyLong_FromSsize_t((ssize_t) o) +#define PYBIND11_LONG_FROM_UNSIGNED(o) PyLong_FromSize_t((size_t) o) #define PYBIND11_BYTES_NAME "bytes" #define PYBIND11_STRING_NAME "str" #define PYBIND11_SLICE_OBJECT PyObject @@ -180,6 +182,8 @@ #define PYBIND11_BYTES_SIZE PyString_Size #define PYBIND11_LONG_CHECK(o) (PyInt_Check(o) || PyLong_Check(o)) #define PYBIND11_LONG_AS_LONGLONG(o) (PyInt_Check(o) ? (long long) PyLong_AsLong(o) : PyLong_AsLongLong(o)) +#define PYBIND11_LONG_FROM_SIGNED(o) PyInt_FromSsize_t((ssize_t) o) // Returns long if needed. +#define PYBIND11_LONG_FROM_UNSIGNED(o) PyInt_FromSize_t((size_t) o) // Returns long if needed. #define PYBIND11_BYTES_NAME "str" #define PYBIND11_STRING_NAME "unicode" #define PYBIND11_SLICE_OBJECT PySliceObject diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index e5413c2c..45081340 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -155,4 +155,9 @@ TEST_SUBMODULE(builtin_casters, m) { // test_complex m.def("complex_cast", [](float x) { return "{}"_s.format(x); }); m.def("complex_cast", [](std::complex x) { return "({}, {})"_s.format(x.real(), x.imag()); }); + + // test int vs. long (Python 2) + m.def("int_cast", []() {return (int) 42;}); + m.def("long_cast", []() {return (long) 42;}); + m.def("longlong_cast", []() {return ULLONG_MAX;}); } diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 2f311f15..01d0437b 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -323,3 +323,16 @@ def test_numpy_bool(): assert convert(np.bool_(False)) is False assert noconvert(np.bool_(True)) is True assert noconvert(np.bool_(False)) is False + + +def test_int_long(): + """In Python 2, a C++ int should return a Python int rather than long + if possible: longs are not always accepted where ints are used (such + as the argument to sys.exit()). A C++ long long is always a Python + long.""" + + import sys + must_be_long = type(getattr(sys, 'maxint', 1) + 1) + assert isinstance(m.int_cast(), int) + assert isinstance(m.long_cast(), int) + assert isinstance(m.longlong_cast(), must_be_long)