Dirk Eddelbuettel: chronometre: A new package (pair) demo for R and Python
Both R and Python make it reasonably easy to work with compiled
extensions. But how to access objects in one environment from the other
and share state or (non-trivial) objects remains trickier.
Recently (and while r-forge was resting so we opened GitHub
Discussions) a question was asked concerning R
and Python object pointer exchange.
This lead to a pretty decent discussion including arrow interchange demos (pretty
ideal if dealing with data.frame-alike objects), but once the focus is
on more library-specific objects from a given (C or C++, say) library
it is less clear what to do, or how involved it may get.
R has external pointers, and these make it feasible to instantiate
the same object in Python. To demonstrate, I created a pair of
(minimal) packages wrapping a lovely (small) class from the excellent spdlog library by Gabi Melman, and more specifically
in an adapted-for-R version (to avoid some R CMD check
nags) in my RcppSpdlog
package. It is essentially a nicer/fancier C++ version of the
tic() and tic() timing scheme. When an object
is instantiated, it starts the clock and when we accessing it later it
prints the time elapsed in microsecond resolution. In Modern C++ this
takes little more than keeping an internal chrono
object.
Which makes for a nice, small, yet specific object to pass to Python.
So the R side of
the package pair instantiates such an object, and accesses its
address. For different reasons, sending a raw pointer across does not
work so well, but a string with the address printed works fabulously
(and is a paradigm used around other packages so we did not invent
this). Over on the Python side of the
package pair, we then take this string representation and pass it to
a little bit of pybind11 code to
instantiate a new object. This can of course also expose functionality
such as the show time elapsed feature, either formatted or just
numerically, of interest here.
And that is all that there is! Now this can be done from R as well
thanks to reticulate
as the demo() (also shown on the package README.md)
shows:
> library(chronometre)
> demo("chronometre", ask=FALSE)
demo(chronometre)
---- ~~~~~~~~~~~
> #!/usr/bin/env r
>
> stopifnot("Demo requires 'reticulate'" = requireNamespace("reticulate", quietly=TRUE))
> stopifnot("Demo requires 'RcppSpdlog'" = requireNamespace("RcppSpdlog", quietly=TRUE))
> stopifnot("Demo requires 'xptr'" = requireNamespace("xptr", quietly=TRUE))
> library(reticulate)
> ## reticulate and Python in general these days really want a venv so we will use one,
> ## the default value is a location used locally; if needed create one
> ## check for existing virtualenv to use, or else set one up
> venvdir <- Sys.getenv("CHRONOMETRE_VENV", "/opt/venv/chronometre")
> if (dir.exists(venvdir))
+ > use_virtualenv(venvdir, required = TRUE)
+ > else
+ > ## create a virtual environment, but make it temporary
+ > Sys.setenv(RETICULATE_VIRTUALENV_ROOT=tempdir())
+ > virtualenv_create("r-reticulate-env")
+ > virtualenv_install("r-reticulate-env", packages = c("chronometre"))
+ > use_virtualenv("r-reticulate-env", required = TRUE)
+ >
> 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.501220
> xptr::is_xptr(sw) # this is an external pointer in R
[1] TRUE
> xptr::xptr_address(sw) # get address, format is "0x...."
[1] "0x58adb5918510"
> 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 close and use
0.501597
> 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=502013)
> cat(sw3$count(), "\n") # shows double
0.502657
> print(sw) # object still works in R
0.502721
> Questions, suggestions, bug reports, are welcome at either the (now awoken from the R-Forge slumber) Rcpp mailing list or the newer Rcpp Discussions.Changes in version 0.0.2 (2026-02-05)
- Removed replaced unconditional virtualenv use in demo given preceding conditional block
- Updated README.md with badges and an updated demo
Changes in version 0.0.1 (2026-01-25)
- Initial version and CRAN upload
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.
Another year of data from Soci t de Transport de Montr al, Montreal's
transit agency!
A few highlights this year: