Interacting with Python code from R has become very convenient via the reticulate package which also takes care of most standard data types used by R users. On top of that, arrow facilitates exchange of vectors and especially data.frame (or in its parlance RecordBatch) objects. Using nanoarrow to do so takes the (considerable) pain of building with arrow away.
But sometimes we want bespoke custom objects from a specific library. R does well with external pointer objects, so a recent question in the Rcpp context was: how do we do this with Python?
This repository has one answer and working demonstration. It uses a very small but clever class: a ‘stopwatch’ implementation taken (with loving appreciation and a nod) from the lovely spdlog library, and specifically the already simplified version in RcppSpdlog presented by this ‘spdlog_stopwatch.h’ file.
It cooperates with the Python package in the sibbling Python repo chronometre-py.
So once the Python package is installed (and a virtual environment for it has been set up), the demo file in this repository and package can be used. It also relies on (CRAN) packages reticulate (to talk to Python), RcppSpdlog for the stopwatch object, and xptr for some convenient external pointer helpers.
It allocates a stopwatch object, demonstrates it, ‘clones’ it via a
the ‘from address string’ constructor in the Python package we access as
object ch provided via the reticulate
import of the Python package.
This generated sample output such as the following:
> library(chronometre)
> demo("chronometre", ask=FALSE)
demo(chronometre)
---- ~~~~~~~~~~~
> #!/usr/bin/env r
>
> library(reticulate)
> ## Python (these days ...) really wants a venv so we will use one, the default
> ## value is a location I used
> use_virtualenv(Sys.getenv("CHRONOMETRE_VENV", "/opt/venv/chronometre"))
> ch <- import("chronometre")
> sw <- RcppSpdlog::get_stopwatch() # we use a C++ struct as example
> Sys.sleep(0.5) # imagine doing some code here
> print(sw) # stopwatch shows elapsed time
0.500918
> xptr::is_xptr(sw) # this is an external pointer in R
[1] TRUE
> xptr::xptr_address(sw) # get address, format is "0x...."
[1] "0x5aa1dbb42a70"
> sw2 <- xptr::new_xptr(xptr::xptr_address(sw)) # cloned (!!) but unclassed
> attr(sw2, "class") <- c("stopwatch", "externalptr") # class it .. and then use it!
> print(sw2) # `xptr` allows us clone and use
0.503156
> sw3 <- ch$Stopwatch( xptr::xptr_address(sw) ) # new Python object via string ctor
> print(sw3$elapsed()) # shows output via Python I/O
datetime.timedelta(microseconds=503619)
> print(sw) # object still works in R
0.504328
> demonstrating that the memory address is in fact the same, and the
behavior is shared. It can also be run via Rscript or
r or via its shebang; in the latter two cases using
r only explicit print() statements show
output: (and we add comments here as illustration)
$ demo/chronometre.R
0.500735 # R object after 500 msec sleep
0.502508 # cloned R object shares that time
datetime.timedelta(microseconds=502929) # so does the new Python object
0.503553 # and original R object still works
$ Dirk Eddelbuettel
GPL (version 2 or later).