diff --git a/src/converter/builtin_converters.cpp b/src/converter/builtin_converters.cpp index 1d8efa29..b5916dde 100644 --- a/src/converter/builtin_converters.cpp +++ b/src/converter/builtin_converters.cpp @@ -74,15 +74,8 @@ namespace if (number_methods == 0) return 0; - // For floating types, return the float conversion slot to avoid - // creating a new object. We'll handle that below - if (PyFloat_Check(obj)) - return &number_methods->nb_float; - - if (PyInstance_Check(obj) && !PyObject_HasAttrString(obj, "__int__")) - return 0; - - return &number_methods->nb_int; + return (PyInt_Check(obj) || PyLong_Check(obj)) + ? &number_methods->nb_int : 0; } }; @@ -91,17 +84,17 @@ namespace { static T extract(PyObject* intermediate) { - return PyFloat_Check(intermediate) - ? numeric_cast(PyFloat_AS_DOUBLE(intermediate)) - : numeric_cast(PyInt_AS_LONG(intermediate)) - ; + return numeric_cast(PyInt_AS_LONG(intermediate)); } }; -// using Python's macro instead of Boost's - we don't seem to get the -// config right all the time. +// Checking Python's macro instead of Boost's - we don't seem to get +// the config right all the time. Furthermore, Python's is defined +// when long long is absent but __int64 is present. + #ifdef HAVE_LONG_LONG // A SlotPolicy for extracting long long types from Python objects + struct long_long_rvalue_from_python_base { static unaryfunc* get_slot(PyObject* obj) @@ -110,22 +103,17 @@ namespace if (number_methods == 0) return 0; - // For floating and integer types, return the identity - // conversion slot to avoid creating a new object. We'll - // handle that in the extract function + // Return the identity conversion slot to avoid creating a + // new object. We'll handle that in the extract function if (PyInt_Check(obj)) return &number_methods->nb_int; - - if (PyFloat_Check(obj)) - return &number_methods->nb_float; - - if (PyInstance_Check(obj) && !PyObject_HasAttrString(obj, "__long__")) + else if (PyLong_Check(obj)) + return &number_methods->nb_long; + else return 0; - - return &number_methods->nb_long; } }; - + struct long_long_rvalue_from_python : long_long_rvalue_from_python_base { static LONG_LONG extract(PyObject* intermediate) @@ -134,10 +122,6 @@ namespace { return PyInt_AS_LONG(intermediate); } - if (PyFloat_Check(intermediate)) - { - return numeric_cast(PyFloat_AS_DOUBLE(intermediate)); - } else { LONG_LONG result = PyLong_AsLongLong(intermediate); @@ -158,10 +142,6 @@ namespace { return numeric_cast(PyInt_AS_LONG(intermediate)); } - if (PyFloat_Check(intermediate)) - { - return numeric_cast(PyFloat_AS_DOUBLE(intermediate)); - } else { unsigned LONG_LONG result = PyLong_AsUnsignedLongLong(intermediate); @@ -175,7 +155,6 @@ namespace }; #endif - // 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 @@ -190,9 +169,9 @@ namespace // A SlotPolicy for extracting bool from a Python object struct bool_rvalue_from_python { - static unaryfunc* get_slot(PyObject*) + static unaryfunc* get_slot(PyObject* obj) { - return &py_object_identity; + return obj == Py_None || PyInt_Check(obj) ? &py_object_identity : 0; } static bool extract(PyObject* intermediate) @@ -214,11 +193,9 @@ namespace // 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; + + return (PyLong_Check(obj) || PyFloat_Check(obj)) + ? &number_methods->nb_float : 0; } static double extract(PyObject* intermediate) @@ -240,10 +217,8 @@ 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; + return (PyString_Check(obj)) + ? &obj->ob_type->tp_str : 0; }; // Remember that this will be used to construct the result object @@ -253,39 +228,14 @@ namespace } }; - - // to_complex_unaryfunc/py_object_to_complex -- manufacture a - // unaryfunc "slot" which calls its argument's __complex__ - // method. We have this because there's no type object nb_complex - // slot. - 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; + else + return float_rvalue_from_python::get_slot(obj); } static std::complex extract(PyObject* intermediate) @@ -300,13 +250,10 @@ namespace { return PyInt_AS_LONG(intermediate); } - else if (!PyFloat_Check(intermediate)) + else { - PyErr_SetString(PyExc_TypeError, "__complex__ method did not return a Complex object"); - throw_error_already_set(); + return PyFloat_AS_DOUBLE(intermediate); } - - return PyFloat_AS_DOUBLE(intermediate); } }; } diff --git a/test/extract.py b/test/extract.py index 999dc7b3..328049dd 100644 --- a/test/extract.py +++ b/test/extract.py @@ -11,7 +11,7 @@ Just about anything has a truth value in Python >>> extract_bool(2) 1 - >>> assert check_bool('') + >>> assert not check_bool('') Check that object manager types work properly. These are a different case because they wrap Python objects instead of being wrapped by them. diff --git a/test/test_builtin_converters.py b/test/test_builtin_converters.py index f2dfa9e4..f6caf56b 100644 --- a/test/test_builtin_converters.py +++ b/test/test_builtin_converters.py @@ -89,8 +89,11 @@ 0 >>> rewrap_const_reference_bool(0) 0 ->>> rewrap_const_reference_bool('yes') -1 + +>>> try: rewrap_const_reference_bool('yes') +... except TypeError: pass +... else: print 'expected a TypeError exception' + >>> rewrap_const_reference_char('x') 'x' @@ -152,25 +155,30 @@ Check that None <==> NULL >>> rewrap_const_reference_cstring(None) -But when converted to a string rvalue, None becomes 'None': - ->>> rewrap_const_reference_string(None) -'None' +But None cannot be converted to a string object: +>>> try: rewrap_const_reference_string(None) +... except TypeError: pass +... else: print 'expected a TypeError exception' Now check implicit conversions between floating/integer types >>> rewrap_const_reference_float(42) 42.0 ->>> rewrap_const_reference_int(42.0) -42 +>>> rewrap_const_reference_float(42L) +42.0 + +>>> try: rewrap_const_reference_int(42.0) +... except TypeError: pass +... else: print 'expected a TypeError exception' >>> rewrap_value_float(42) 42.0 ->>> rewrap_value_int(42.0) -42 +>>> try: rewrap_value_int(42.0) +... except TypeError: pass +... else: print 'expected a TypeError exception' Check that classic classes also work @@ -184,14 +192,21 @@ Check that classic classes also work ... 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 +>>> try: rewrap_const_reference_float(FortyTwo()) +... except TypeError: pass +... else: print 'expected a TypeError exception' + +>>> try: rewrap_value_int(FortyTwo()) +... except TypeError: pass +... else: print 'expected a TypeError exception' + +>>> try: rewrap_const_reference_string(FortyTwo()) +... except TypeError: pass +... else: print 'expected a TypeError exception' + +>>> try: rewrap_value_complex_double(FortyTwo()) +... except TypeError: pass +... else: print 'expected a TypeError exception' # show that arbitrary handle instantiations can be returned >>> get_type(1) is type(1) @@ -204,7 +219,13 @@ Check that classic classes also work def run(args = None): import sys import doctest - + import builtin_converters + + if 'rewrap_value_long_long' in dir(builtin_converters): + print 'LONG_LONG supported, testing...' + else: + print 'LONG_LONG not supported, skipping those tests...' + if args is not None: sys.argv = args return doctest.testmod(sys.modules.get(__name__))