diff --git a/src/python.cpp b/src/python.cpp index 50dea48b..2693fbbf 100644 --- a/src/python.cpp +++ b/src/python.cpp @@ -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) { @@ -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; } @@ -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]")); @@ -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) { @@ -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); diff --git a/tests/testthat/test-python-numpy.R b/tests/testthat/test-python-numpy.R index 75056f05..41a11224 100644 --- a/tests/testthat/test-python-numpy.R +++ b/tests/testthat/test-python-numpy.R @@ -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))) + +})