Skip to content

Commit

Permalink
Merge pull request #1552 from rstudio/py_to_r-refactor
Browse files Browse the repository at this point in the history
`py_to_r` refactor (c++)
  • Loading branch information
t-kalinowski authored Mar 20, 2024
2 parents 129b6f5 + 1906d01 commit 7fba309
Show file tree
Hide file tree
Showing 25 changed files with 1,412 additions and 978 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ jobs:
- { os: ubuntu-latest, r: 'oldrel-1', python: '3.9' }
- { os: ubuntu-latest, r: 'oldrel-2', python: '3.9' }
- { os: ubuntu-20.04, r: 'oldrel-3', python: '3.8' }
# - { os: ubuntu-20.04, r: 'oldrel-4', python: '3.8' }
- { os: ubuntu-20.04, r: 'oldrel-4', python: '3.8' }
# https://api.r-hub.io/rversions/resolve/oldrel/4
# - { os: ubuntu-latest, r: 'devel' , python: '3.8', http-user-agent: 'release' }

- { os: ubuntu-latest, r: 'release', python: '3.7' }
- { os: ubuntu-latest, r: 'release', python: '3.8' }
- { os: ubuntu-latest, r: 'release', python: '3.10' }
- { os: ubuntu-latest, r: 'release', python: '3.11' }
- { os: ubuntu-latest, r: 'release', python: '3.12' }

# - { os: ubuntu-latest, r: 'devel', python: '3.12', http-user-agent: 'release' }

# test with one debug build of python
# disabled; failing presently
# - { os: ubuntu-latest , r: 'release', python: 'debug' }
Expand Down Expand Up @@ -73,7 +74,7 @@ jobs:
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: rcmdcheck remotes local::.
extra-packages: rcmdcheck local::.
cache-version: 3
upgrade: 'TRUE'

Expand Down
6 changes: 0 additions & 6 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ S3method("!",python.builtin.object)
S3method("!=",python.builtin.object)
S3method("$",python.builtin.BaseException)
S3method("$",python.builtin.dict)
S3method("$",python.builtin.module)
S3method("$",python.builtin.object)
S3method("$<-",python.builtin.dict)
S3method("$<-",python.builtin.object)
Expand Down Expand Up @@ -84,16 +83,12 @@ S3method(py_to_r,datatable.Frame)
S3method(py_to_r,datetime.date)
S3method(py_to_r,datetime.datetime)
S3method(py_to_r,default)
S3method(py_to_r,numpy.ndarray)
S3method(py_to_r,pandas._libs.missing.C_NAType)
S3method(py_to_r,pandas._libs.missing.NAType)
S3method(py_to_r,pandas.core.arrays.categorical.Categorical)
S3method(py_to_r,pandas.core.categorical.Categorical)
S3method(py_to_r,pandas.core.frame.DataFrame)
S3method(py_to_r,pandas.core.series.Series)
S3method(py_to_r,python.builtin.dict)
S3method(py_to_r,python.builtin.list)
S3method(py_to_r,python.builtin.tuple)
S3method(py_to_r,scipy.sparse._base.spmatrix)
S3method(py_to_r,scipy.sparse._coo.coo_matrix)
S3method(py_to_r,scipy.sparse._csc.csc_matrix)
Expand All @@ -113,7 +108,6 @@ S3method(r_to_py,dgRMatrix)
S3method(r_to_py,dgTMatrix)
S3method(r_to_py,error)
S3method(r_to_py,factor)
S3method(r_to_py,list)
S3method(r_to_py,sparseMatrix)
S3method(str,py_config)
S3method(str,python.builtin.module)
Expand Down
20 changes: 20 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# reticulate (development version)

- Fixed issue where callable python objects created with `convert = FALSE` would not be
wrapped in an R function.

- Fixed issue where `py_to_r()` S3 methods would not be called on arguments supplied to
R functions being called from Python.

- py_to_r(x) now returns `x` unmodified if `x` is not a Python object, instead of signaling an error.

- `attr(x, "tzone")` attributes are (better) preserved when converting POSIXt to Python.
POSIXt types with a non-empty `tzone` attr convert to a datetime.datetime,
otherwise they convert to NumPy datetime64[ns] arrays.

- Fixed an issue where calling py_set_item() on a subclassed dict would
not invoke a custom __setitem__ method.

- py_del_attr(x, name) now returns x invisibly

- source_python() no longer exports assigns the "r" symbol to the R globalenv().
(the "R Interface object" that is used by python code get a reference to the R globalenv)

- `iterate(simplify=TRUE)` rewritten in C for speed improvements.

- Fixed hang encountered (sometimes) when attempting to call `iterate()`
Expand Down
54 changes: 42 additions & 12 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ is_python3 <- function() {
.Call(`_reticulate_is_python3`)
}

was_python_initialized_by_reticulate <- function() {
.Call(`_reticulate_was_python_initialized_by_reticulate`)
}

#' Check if a Python object is a null externalptr
#'
#' @param x Python object
Expand Down Expand Up @@ -56,6 +60,14 @@ py_is_callable <- function(x) {
.Call(`_reticulate_py_is_callable`, x)
}

is_py_object <- function(x) {
.Call(`_reticulate_is_py_object`, x)
}

py_to_r_cpp <- function(x) {
.Call(`_reticulate_py_to_r_cpp`, x)
}

py_get_formals <- function(callable) {
.Call(`_reticulate_py_get_formals`, callable)
}
Expand All @@ -76,10 +88,6 @@ py_clear_error <- function() {
invisible(.Call(`_reticulate_py_clear_error`))
}

was_python_initialized_by_reticulate <- function() {
.Call(`_reticulate_was_python_initialized_by_reticulate`)
}

py_initialize <- function(python, libpython, pythonhome, virtualenv_activate, python3, interactive, numpy_load_error) {
invisible(.Call(`_reticulate_py_initialize`, python, libpython, pythonhome, virtualenv_activate, python3, interactive, numpy_load_error))
}
Expand Down Expand Up @@ -130,6 +138,18 @@ py_get_attr_impl <- function(x, key, silent = FALSE) {
.Call(`_reticulate_py_get_attr_impl`, x, key, silent)
}

py_get_convert <- function(x) {
.Call(`_reticulate_py_get_convert`, x)
}

py_set_convert <- function(x, value) {
.Call(`_reticulate_py_set_convert`, x, value)
}

py_new_ref <- function(x, convert) {
.Call(`_reticulate_py_new_ref`, x, convert)
}

py_get_item_impl <- function(x, key, silent = FALSE) {
.Call(`_reticulate_py_get_item_impl`, x, key, silent)
}
Expand All @@ -146,6 +166,10 @@ py_set_item_impl <- function(x, key, val) {
invisible(.Call(`_reticulate_py_set_item_impl`, x, key, val))
}

py_del_item_impl <- function(x, key) {
invisible(.Call(`_reticulate_py_del_item_impl`, x, key))
}

py_get_attr_types_impl <- function(x, attrs, resolve_properties) {
.Call(`_reticulate_py_get_attr_types_impl`, x, attrs, resolve_properties)
}
Expand Down Expand Up @@ -206,14 +230,6 @@ py_list_submodules <- function(module) {
.Call(`_reticulate_py_list_submodules`, module)
}

py_iterate <- function(x, f, simplify = TRUE) {
.Call(`_reticulate_py_iterate`, x, f, simplify)
}

py_iter_next <- function(iterator, completed) {
.Call(`_reticulate_py_iter_next`, iterator, completed)
}

py_run_string_impl <- function(code, local = FALSE, convert = TRUE) {
.Call(`_reticulate_py_run_string_impl`, code, local, convert)
}
Expand Down Expand Up @@ -286,6 +302,20 @@ py_slice <- function(start = NULL, stop = NULL, step = NULL) {
.Call(`_reticulate_py_slice`, start, stop, step)
}

#' @rdname iterate
#' @export
as_iterator <- function(x) {
.Call(`_reticulate_as_iterator`, x)
}

py_iter_next <- function(iterator, completed) {
.Call(`_reticulate_py_iter_next`, iterator, completed)
}

py_iterate <- function(x, f, simplify = TRUE) {
.Call(`_reticulate_py_iterate`, x, f, simplify)
}

readline <- function(prompt) {
.Call(`_reticulate_readline`, prompt)
}
Expand Down
3 changes: 1 addition & 2 deletions R/array.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ np_array <- function(data, dtype = NULL, order = "C") {
data <- if (isobj) {
r_to_py(data, convert = FALSE)
} else {
ensure_python_initialized()
r_to_py_impl(data, convert = FALSE)
}
}
Expand Down Expand Up @@ -93,7 +92,7 @@ array_reshape <- function(x, dim, order = c("C", "F")) {
np <- import("numpy", convert = FALSE)
order <- match.arg(order)
reshaped <- np$reshape(x, as.integer(dim), order)
if (!inherits(x, "python.builtin.object"))
if (!is_py_object(x))
reshaped <- py_to_r(reshaped)
reshaped
}
Expand Down
52 changes: 26 additions & 26 deletions R/class.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inject_super <- function(fun) {
# the `self` argument passed to `fun`.

e <- new.env(parent = environment(fun))

e$super <- function() {
bt <- reticulate::import_builtins()
# self is an argument passed to fun
Expand All @@ -19,14 +19,14 @@ inject_super <- function(fun) {
}

#' Create a python class
#'
#'
#' @param classname Name of the class. The class name is useful for S3 method
#' dispatch.
#' @param defs A named list of class definitions - functions, attributes, etc.
#' @param inherit A list of Python class objects. Usually these objects have
#' the `python.builtin.type` S3 class.
#'
#' @examples
#'
#' @examples
#' \dontrun{
#' Hi <- PyClass("Hi", list(
#' name = NULL,
Expand All @@ -38,68 +38,68 @@ inject_super <- function(fun) {
#' paste0("Hi ", self$name)
#' }
#' ))
#'
#'
#' a <- Hi("World")
#' }
#'
#' @export
PyClass <- function(classname, defs = list(), inherit = NULL) {

builtins <- import_builtins(convert = TRUE)
if (inherits(inherit, "python.builtin.object"))

if (is_py_object(inherit))
inherit <- list(inherit)

bases <- case(

length(inherit) == 0 ~ tuple(),
is.list(inherit) ~ do.call(tuple, inherit),
is.character(inherit) ~ do.call(tuple, as.list(inherit)),

~ stop("unexpected 'inherit' argument")

)

defs <- lapply(defs, function(x) {

# nothing to be done for non-functions
if (!is.function(x))
return(x)

# otherwise, create a new version of the function with 'super' injected
f <- inject_super(x)

x <- function(...) {
# enable conversion scope for `self`
# the first argument is always `self`.and we don't want to convert it.
args <- list(...)
assign("convert", TRUE, envir = as.environment(args[[1]]))
assign("convert", TRUE, envir = as.environment(args[[1]]))
do.call(f, append(args[1], lapply(args[-1], py_to_r)))
}

attr(x, "__env__") <- environment(f)
x

})

type <- builtins$type(
classname,
bases,
do.call(reticulate::dict, defs)
)

# we add a reference to the type here. so it can be accessed without needing
# to find the type from self.
lapply(defs, function(x) {

envir <- attr(x, "__env__")
if (!is.environment(envir))
return()

envir$`__class__` <- type

})

type

}
Loading

0 comments on commit 7fba309

Please sign in to comment.