2

I am trying to pass an argument by reference to a Python function from C++ using the Boost Python library (as well as the Boost Numpy extension). The object in question is an ndarray, and passing it by value works just fine as demonstrated below:

#include <boost/python/numpy.hpp>
#include <cstdlib>
#include <iostream>
#include <array>

namespace p = boost::python;
namespace np = boost::python::numpy;


int main(int argc, char* argv[]) {
    // Set PYTHONPATH 
    setenv("PYTHONPATH", ".", 1);

    // Initialize Python 
    Py_Initialize();

    // Initialize Numpy 
    np::initialize();

    // Create array of 5 integers
    std::array<int,5> arr = { 2,4,6,8,10};

    // Create Numpy array of ints.
    p::tuple shape = p::make_tuple(5);
    np::dtype dtype = np::dtype::get_builtin<int>();
    np::ndarray np_arr = np::from_data(arr.data(),
        dtype,
        shape,
        p::make_tuple(sizeof(int)),
        p::object());

    try {
        // Import Python module.
        p::object python_module = 
        p::import("pytest");

        // Pull class from module.
        p::object start_class = python_module.attr("Foo")();

        // Call class function with argument
        start_class.attr("bar")(np_arr);

        // Call function again
        start_class.attr("bar")(np_arr);
    }
    catch (const boost::python::error_already_set&) {
        // Print Python Errors
        PyErr_Print();
    }

    return 0;
}

The relevant pytest.py file is as follows:

class Foo:
    def bar(self, x):
        print(x)
        x = 2*x

This prints out [ 2 4 6 8 10] [ 2 4 6 8 10] as expected. However, I would like for the ndarray to be passed by reference in order to avoid any expensive copies (i.e. the output would be [ 2 4 6 8 10] [ 4 8 12 16 20]).

According to the documentation, the way to do this seems to be through boost::ref. However, when I wrap the argument as a reference (by changing the function call to start_class.attr("bar")(boost::ref(np_arr));) I get the following error message from Python: TypeError: No Python class registered for C++ class boost::python::numpy::ndarray

Hufh294
  • 89
  • 5
  • Your `bar()` function doesn't mutate the original value of `x`. That's not how assignment works in Python. `x` is a *label* initially bound to an object with the value `[2,4,6,8,10]`. The right-hand side of the assignment creates a *new* object with the value `[4,8,12,16,20]` and then binds the label `x` to that new object. The original object to which `x` was bound, `[2,4,6,8,10]`, now has no label attached to it and is then garbage-collected. See here: https://nedbatchelder.com/text/names.html – jjramsey Aug 05 '22 at 13:43
  • I would suggest replacing `x = 2*x` with `x *= 2`. That *should* mutate `x`. You may find that you already have been effectively passing references (or handles) rather that copying the array data. – jjramsey Aug 05 '22 at 13:49
  • 2
    Lists/arrays are passed by reference in Python. Therefore you shouldn't need to use `boost::ref`. As @jjramsey says in another comment, the script is wrong. `x = 2*x` doesn't mutate the array passed to the function `bar`. It assigns a new a new object to `x`. You can use the slice operator to mutate the array passed in: `x[:] = 2*x`. See [this answer](https://stackoverflow.com/questions/22054698/modifying-a-list-inside-a-function/22055029#22055029) for better explanation. – jignatius Aug 11 '22 at 02:45

0 Answers0