Skip to content

Commit

Permalink
Merge pull request #1614 from rstudio/py_to_r-fallback-non-simple-np-…
Browse files Browse the repository at this point in the history
…types

`py_to_r()` fallback with non-simple NumPy types
  • Loading branch information
t-kalinowski authored May 28, 2024
2 parents 16aa382 + 9133b29 commit 8952394
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 3 deletions.
15 changes: 12 additions & 3 deletions src/python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ bool has_null_bytes(PyObject* str) {
}

// helpers to narrow python array type to something convertable from R,
// guaranteed to return NPY_BOOL, NPY_LONG, NPY_DOUBLE, or NPY_CDOUBLE
// (throws an exception if it's unable to return one of these types)
// guaranteed to return NPY_BOOL, NPY_LONG, NPY_DOUBLE, NPY_CDOUBLE,
// or -1 if it's unable to return one of these types.
int narrow_array_typenum(int typenum) {

switch(typenum) {
Expand Down Expand Up @@ -412,7 +412,7 @@ int narrow_array_typenum(int typenum) {

// unsupported
default:
stop("Conversion from numpy array type %d is not supported", typenum);
typenum = -1;
break;
}

Expand Down Expand Up @@ -1432,6 +1432,10 @@ SEXP py_to_r_cpp(PyObject* x, bool convert, bool simple) {
// determine the target type of the array
int og_typenum = PyArray_TYPE(array);
int typenum = narrow_array_typenum(og_typenum);
if (typenum == -1) {
simple = false;
goto cant_convert;
}

if(og_typenum == NPY_DATETIME) {
PyObjectPtr dtype_str(as_python_str("datetime64[ns]"));
Expand Down Expand Up @@ -1581,6 +1585,10 @@ SEXP py_to_r_cpp(PyObject* x, bool convert, bool simple) {
PyArray_DescrPtr descrPtr(PyArray_DescrFromScalar(x));
int og_typenum = descrPtr.get()->type_num;
int typenum = narrow_array_typenum(og_typenum);
if (typenum == -1) {
simple = false;
goto cant_convert;
}

PyObjectPtr x_;
if(og_typenum == NPY_DATETIME) {
Expand Down Expand Up @@ -1675,6 +1683,7 @@ SEXP py_to_r_cpp(PyObject* x, bool convert, bool simple) {

} // end convert == true && simple == true

cant_convert:
Py_IncRef(x);
return PyObjectRef(x, convert, simple);

Expand Down
40 changes: 40 additions & 0 deletions tests/testthat/test-python-numpy.R
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,43 @@ test_that("numpy string arrays are correctly handled", {
"17", "18"), byrow = TRUE, ncol = 2))

})


test_that("numpy non-simple arrays work", {
# https://github.com/rstudio/reticulate/issues/1613
py_run_string("import numpy as np", convert = FALSE)
py_run_string(
"array = np.array([(1.0, 2), (3.0, 4)], dtype=[('x', float), ('y', int)])",
convert = FALSE
)
result <- py_run_string("rec_array = array.view(np.recarray)", convert = FALSE)

# Test that attempting to convert a non-simple array fails gracefully,
# returns a PyObjectRef.
rec_array <- py_to_r(result$rec_array)
expect_equal(class(rec_array),
c("numpy.recarray", "numpy.ndarray", "python.builtin.object"))

# Test that a registered S3 method for the non-simple numpy array will be
# called. (Note, some packages, like {zellkonverter}, will register this
# directly for numpy.ndarray)
registerS3method("py_to_r", "numpy.recarray", function(x) {
tryCatch({
pandas <- import("pandas", convert = FALSE)
x <- pandas$DataFrame(x)$to_numpy()
x <- py_to_r(x)
return(x)
}, error = identity)
NextMethod()
})

on.exit({
rm(list = "py_to_r.numpy.recarray",
envir = environment(py_to_r)$.__S3MethodsTable__.)
})

arr <- py_to_r(result$rec_array)
expect_identical(arr, rbind(c(1, 2),
c(3, 4)))

})

0 comments on commit 8952394

Please sign in to comment.