pythonc++complex-numberspybind11callable

in pybind11, how do you test if py::object is a complex number or a callable?


Context: I've recently been using pybind11 to put a python frontend on an old c++ project of mine. This has mostly been a breeze (thanks pybind11 devs!), but there are some areas where I've had to resort to some really ugly hacks that I'd rather like to avoid.

Question: When translating from py::object (user input in python) to a class in my code that acts a lot like py::object (call it myobject), I need to detect the type of py::object. This is trivial for int, float, string, list etc, but I can't see an obvious way for complex numbers and function handles. Here's my current workaround for complex numbers:

bool isComplex(py::object src)
{
    // put src into a buffer accessible from both C++ and python (i is an index in this buffer)
    int i = pyosetsrc(src); 

    // Build a python function to do the "is it complex" test
    std::string fn;
    fn = "isinstance(mycode.pyogetsrc("; // pyogetsrc(i) gets src back out of the shared buffer
    fn += std::to_string(i);
    fn += "), complex)";

    // Wrap this in an eval statement
    std::string evalfn;
    evalfn = "eval('";
    evalfn += fn;
    evalfn += "')";

    // Run the eval statement
    py::object builtins = py::module_::import("builtins");
    py::object eval = builtins.attr("eval");
    py::object res = eval(evalfn);

    // Process the result
    return py::isinstance<py::bool_>(res) && py::cast<py::bool_>(res);
}

and for function handles there's a similar hack, this time with:

    fn = "callable(mycode.pyogetsrc(";
    fn += std::to_string(i);
    fn += "))";

is there a less awful alternative I'm missing here?


Solution

  • all of isinstance and callable and complex are in the builtin module, you just need to get them and call them.

    bool is_complex(py::object src)
    {
        py::object builtins = py::module_::import("builtins");
        py::object isinstance_function = builtins.attr("isinstance");
        py::object complex_class = builtins.attr("complex");
        
        // to test if an object is callable
        py::object callable_function = builtins.attr("callable"); 
        
        // if not bool let exception be thrown
        return py::cast<py::bool_>(isinstance_function(src, complex_class)); 
    }
    
    import pybind_example
    
    a = 5+4j
    print(pybind_example.is_complex(a))  # prints True
    

    you could store those objects somewhere to avoid importing them every time .... unless you are planning on using multiple interpreters or restarting the interpreter.