Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

py_to_r refactor (c++) #1552

Merged
merged 38 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1061801
import C func `PyIter_Check()`
t-kalinowski Mar 14, 2024
264dfd3
simplify `iter_next()`
t-kalinowski Mar 14, 2024
29c91b6
move as_iterator to c++
t-kalinowski Mar 14, 2024
6685487
simplify+move py_iterate(), py_iter_next()
t-kalinowski Mar 14, 2024
41aacc0
fix (silently failing) pandas datetime test
t-kalinowski Mar 18, 2024
c5fa914
use `test_path()` in test `source_python()`
t-kalinowski Mar 18, 2024
657bfb1
fix silently failing py_dict test
t-kalinowski Mar 18, 2024
f1aec61
delete special external ptr capsule codepath (covered now by general …
t-kalinowski Mar 18, 2024
335127c
conditionally use `PyIter_Check` (available starting in Python 3.10)
t-kalinowski Mar 18, 2024
e4cda38
initialze common SEXPs in pkg init.
t-kalinowski Mar 18, 2024
14d22fb
handle POSIXt conversion to numpy in c++
t-kalinowski Mar 18, 2024
d885c5e
updates for new PyObjectRef
t-kalinowski Mar 18, 2024
d4c9f4b
move PyErrorScopeGuard
t-kalinowski Mar 18, 2024
da1c85f
py_dict_set_item - find custom __setitem__ methods
t-kalinowski Mar 18, 2024
058fac9
avoid repeated eval of constants
t-kalinowski Mar 18, 2024
beb3ce6
convert logical arrays to numpy arrays with a strided view, avoid ful…
t-kalinowski Mar 18, 2024
11ba1de
refactor py_to_r (c++ ver)
t-kalinowski Mar 18, 2024
c645db1
update py_to_r() R functions
t-kalinowski Mar 18, 2024
7b0f8e6
update r_to_py and py_to_r methods for datetimes/ POSIXt
t-kalinowski Mar 18, 2024
c3fe9a5
pandas conversion fixes
t-kalinowski Mar 18, 2024
1881a7b
source_python: don't export 'r'
t-kalinowski Mar 18, 2024
5b0b30d
simplify `py_maybe_convert()`
t-kalinowski Mar 18, 2024
093f915
remove module_proxy checks; simplify r getitem and getattr
t-kalinowski Mar 18, 2024
a5951f8
r utils
t-kalinowski Mar 18, 2024
9894670
handle pandas na conversion in S3 methods
t-kalinowski Mar 18, 2024
225fb13
call S3 generics from c++ in userenv
t-kalinowski Mar 18, 2024
caf60b5
checkin updated PyObjectRef
t-kalinowski Mar 18, 2024
ed1e70b
redocument
t-kalinowski Mar 18, 2024
715eaa5
move py_del_item to c++
t-kalinowski Mar 19, 2024
64a1729
consolidate py_initialize and py_flush guardrails in cpp
t-kalinowski Mar 19, 2024
86a8c57
expand conversion tests
t-kalinowski Mar 19, 2024
cab0226
fix tests
t-kalinowski Mar 19, 2024
82048e9
expand CI
t-kalinowski Mar 19, 2024
11fc42d
more tests
t-kalinowski Mar 19, 2024
ad6b8aa
proofread + fixes
t-kalinowski Mar 19, 2024
ce917fd
add NEWS
t-kalinowski Mar 19, 2024
a713a32
address review feedback
t-kalinowski Mar 19, 2024
1906d01
add tests
t-kalinowski Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading