diff --git a/include/boost/python/converter/builtin_converters.hpp b/include/boost/python/converter/builtin_converters.hpp index 8293b0a7..7ae7a357 100644 --- a/include/boost/python/converter/builtin_converters.hpp +++ b/include/boost/python/converter/builtin_converters.hpp @@ -8,6 +8,7 @@ # include # include # include +# include namespace boost { namespace python { @@ -58,6 +59,9 @@ BOOST_PYTHON_TO_PYTHON_BY_VALUE(float, PyFloat_FromDouble(x)) BOOST_PYTHON_TO_PYTHON_BY_VALUE(double, PyFloat_FromDouble(x)) BOOST_PYTHON_TO_PYTHON_BY_VALUE(long double, PyFloat_FromDouble(x)) BOOST_PYTHON_TO_PYTHON_BY_VALUE(PyObject*, x ? x : detail::none()) +BOOST_PYTHON_TO_PYTHON_BY_VALUE(std::complex, PyComplex_FromDoubles(x.real(), x.imag())) +BOOST_PYTHON_TO_PYTHON_BY_VALUE(std::complex, PyComplex_FromDoubles(x.real(), x.imag())) +BOOST_PYTHON_TO_PYTHON_BY_VALUE(std::complex, PyComplex_FromDoubles(x.real(), x.imag())) namespace converter { diff --git a/src/converter/builtin_converters.cpp b/src/converter/builtin_converters.cpp index 8db20586..2e9bcbec 100644 --- a/src/converter/builtin_converters.cpp +++ b/src/converter/builtin_converters.cpp @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include namespace boost { namespace python { namespace converter { @@ -73,15 +75,18 @@ namespace // For floating types, return the float conversion slot to avoid // creating a new object. We'll handle that below - if (PyObject_TypeCheck(obj, &PyFloat_Type) && number_methods->nb_float) + if (PyFloat_Check(obj)) return &number_methods->nb_float; - + + if (PyInstance_Check(obj) && !PyObject_HasAttrString(obj, "__int__")) + return 0; + return &number_methods->nb_int; } static long extract(PyObject* intermediate) { - if (PyObject_TypeCheck(intermediate, &PyFloat_Type)) + if (PyFloat_Check(intermediate)) { return numeric_cast(PyFloat_AS_DOUBLE(intermediate)); } @@ -93,22 +98,23 @@ namespace }; - // identity_unaryfunc/non_null -- manufacture a unaryfunc "slot" - // which just returns its argument. Used for bool conversions, since - // all Python objects are directly convertible to bool + // identity_unaryfunc/py_object_identity -- manufacture a unaryfunc + // "slot" which just returns its argument. Used for bool + // conversions, since all Python objects are directly convertible to + // bool extern "C" PyObject* identity_unaryfunc(PyObject* x) { Py_INCREF(x); return x; } - unaryfunc non_null = identity_unaryfunc; + unaryfunc py_object_identity = identity_unaryfunc; // A SlotPolicy for extracting bool from a Python object struct bool_rvalue_from_python { static unaryfunc* get_slot(PyObject*) { - return &non_null; + return &py_object_identity; } static bool extract(PyObject* intermediate) @@ -127,17 +133,19 @@ namespace return 0; // For integer types, return the tp_int conversion slot to avoid - // creating a new object. We'll handle that in - // py_float_or_int_as_double, below - if (PyObject_TypeCheck(obj, &PyInt_Type) && number_methods->nb_int) + // creating a new object. We'll handle that below + if (PyInt_Check(obj)) return &number_methods->nb_int; + if (PyInstance_Check(obj) && !PyObject_HasAttrString(obj, "__float__")) + return 0; + return &number_methods->nb_float; } static double extract(PyObject* intermediate) { - if (PyObject_TypeCheck(intermediate, &PyInt_Type)) + if (PyInt_Check(intermediate)) { return PyInt_AS_LONG(intermediate); } @@ -154,6 +162,9 @@ namespace // If the underlying object is "string-able" this will succeed static unaryfunc* get_slot(PyObject* obj) { + if (PyInstance_Check(obj) && !PyObject_HasAttrString(obj, "__str__")) + return 0; + return &obj->ob_type->tp_str; }; @@ -163,6 +174,64 @@ namespace return PyString_AsString(intermediate); } }; + + + // identity_unaryfunc/non_null -- manufacture a unaryfunc "slot" + // which just returns its argument. Used for bool conversions, since + // all Python objects are directly convertible to bool + extern "C" PyObject* to_complex_unaryfunc(PyObject* x) + { + return PyObject_CallMethod(x, "__complex__", const_cast("()")); + } + unaryfunc py_object_to_complex = to_complex_unaryfunc; + + struct complex_rvalue_from_python + { + static unaryfunc* get_slot(PyObject* obj) + { + + if (PyComplex_Check(obj)) + return &py_object_identity; + + PyNumberMethods* number_methods = obj->ob_type->tp_as_number; + + // For integer types, return the tp_int conversion slot to avoid + // creating a new object. We'll handle that below + if (PyInt_Check(obj) && number_methods) + return &number_methods->nb_int; + + if (PyFloat_Check(obj) && number_methods) + return &number_methods->nb_float; + + if (!PyObject_HasAttrString((PyObject*)obj, "__complex__")) + return 0; + + return &py_object_to_complex; + } + + static std::complex extract(PyObject* intermediate) + { + if (PyComplex_Check(intermediate)) + { + return std::complex( + PyComplex_RealAsDouble(intermediate) + , PyComplex_ImagAsDouble(intermediate)); + } + else if (PyInt_Check(intermediate)) + { + return PyInt_AS_LONG(intermediate); + } + else if (PyFloat_Check(intermediate)) + { + return PyFloat_AS_DOUBLE(intermediate); + } + else + { + PyErr_SetString(PyExc_TypeError, "__complex__ method did not return a Complex object"); + throw error_already_set(); + } + } + }; } #define REGISTER_INT_CONVERTERS(U) slot_rvalue_from_python() @@ -184,6 +253,10 @@ void initialize_builtin_converters() slot_rvalue_from_python(); slot_rvalue_from_python(); + slot_rvalue_from_python,complex_rvalue_from_python>(); + slot_rvalue_from_python,complex_rvalue_from_python>(); + slot_rvalue_from_python,complex_rvalue_from_python>(); + // Add an lvalue converter for char which gets us char const* registry::insert(convert_to_cstring,undecorated_type_id()); diff --git a/test/test_builtin_converters.cpp b/test/test_builtin_converters.cpp index f95ebe2b..4c07d9f4 100644 --- a/test/test_builtin_converters.cpp +++ b/test/test_builtin_converters.cpp @@ -5,7 +5,7 @@ // to its suitability for any purpose. #include #include - +#include template struct by_value @@ -41,6 +41,9 @@ BOOST_PYTHON_MODULE_INIT(builtin_converters_ext) .def("rewrap_value_float", by_value::rewrap) .def("rewrap_value_double", by_value::rewrap) .def("rewrap_value_long_double", by_value::rewrap) + .def("rewrap_value_complex_float", by_value >::rewrap) + .def("rewrap_value_complex_double", by_value >::rewrap) + .def("rewrap_value_complex_long_double", by_value >::rewrap) .def("rewrap_value_string", by_value::rewrap) .def("rewrap_value_cstring", by_value::rewrap) @@ -57,6 +60,9 @@ BOOST_PYTHON_MODULE_INIT(builtin_converters_ext) .def("rewrap_const_reference_float", by_const_reference::rewrap) .def("rewrap_const_reference_double", by_const_reference::rewrap) .def("rewrap_const_reference_long_double", by_const_reference::rewrap) + .def("rewrap_const_reference_complex_float", by_const_reference >::rewrap) + .def("rewrap_const_reference_complex_double", by_const_reference >::rewrap) + .def("rewrap_const_reference_complex_long_double", by_const_reference >::rewrap) .def("rewrap_const_reference_string", by_const_reference::rewrap) .def("rewrap_const_reference_cstring", by_const_reference::rewrap) diff --git a/test/test_builtin_converters.py b/test/test_builtin_converters.py index 50b58ce6..3a4722c7 100644 --- a/test/test_builtin_converters.py +++ b/test/test_builtin_converters.py @@ -30,6 +30,13 @@ >>> rewrap_value_long_double(4.2) - 4.2 0.0 +>>> abs(rewrap_value_complex_float(4+.2j) - (4+.2j)) < .000001 +1 +>>> abs(rewrap_value_complex_double(4+.2j) - (4+.2j)) < .000001 +1 +>>> abs(rewrap_value_complex_long_double(4+.2j) - (4+.2j)) < .000001 +1 + >>> rewrap_value_cstring('hello, world') 'hello, world' >>> rewrap_value_string('yo, wassup?') @@ -57,6 +64,7 @@ 42 >>> rewrap_const_reference_unsigned_long(42) 42 + >>> abs(rewrap_const_reference_float(4.2) - 4.2) < .000001 1 >>> rewrap_const_reference_double(4.2) - 4.2 @@ -64,6 +72,13 @@ >>> rewrap_const_reference_long_double(4.2) - 4.2 0.0 +>>> abs(rewrap_const_reference_complex_float(4+.2j) - (4+.2j)) < .000001 +1 +>>> abs(rewrap_const_reference_complex_double(4+.2j) - (4+.2j)) < .000001 +1 +>>> abs(rewrap_const_reference_complex_long_double(4+.2j) - (4+.2j)) < .000001 +1 + >>> rewrap_const_reference_cstring('hello, world') 'hello, world' >>> rewrap_const_reference_string('yo, wassup?') @@ -83,6 +98,27 @@ Now check implicit conversions between floating/integer types >>> rewrap_value_int(42.0) 42 +Check that classic classes also work + +>>> class FortyTwo: +... def __int__(self): +... return 42 +... def __float__(self): +... return 42.0 +... def __complex__(self): +... return complex(4+.2j) +... def __str__(self): +... return '42' + +>>> rewrap_const_reference_float(FortyTwo()) +42.0 +>>> rewrap_value_int(FortyTwo()) +42 +>>> rewrap_const_reference_string(FortyTwo()) +'42' +>>> abs(rewrap_value_complex_double(FortyTwo()) - (4+.2j)) < .000001 +1 + """ def run(args = None): import sys