1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
|
inject_super <- function(fun) {
# for each function `fun` we need to place a `super` function in its
# search path, and this `super` function must be able to access
# 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
self <- get("self", envir = parent.frame(), inherits = FALSE)
class_ <- get("__class__", envir = e, inherits = FALSE)
bt$super(class_, self)
}
environment(fun) <- e # so fun can access `super`
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
#' \dontrun{
#' Hi <- PyClass("Hi", list(
#' name = NULL,
#' `__init__` = function(self, name) {
#' self$name <- name
#' NULL
#' },
#' say_hi = function(self) {
#' paste0("Hi ", self$name)
#' }
#' ))
#'
#' a <- Hi("World")
#' }
#'
#' @export
PyClass <- function(classname, defs = list(), inherit = NULL) {
builtins <- import_builtins(convert = TRUE)
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]]))
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
}
|