如何解决Python 和 R 之间的互操作性 Coda:从 Python 执行任意 R 代码
从 R 函数内部从 Python(使用 reticulate
)读取变量的假设方法是什么?
由于Python会话无法访问函数环境中的变量,是否只能将它们复制到全局环境中?
library(reticulate)
library(glue)
library(tidyverse)
df1 <- data.frame(col1 = c(123,234),col2 = c(233,283))
py_run_string("print (r.df1)")
#> col1 col2
#> 0 123.0 233.0
#> 1 234.0 283.0
fun <- function(x) {
# create a new variable with a random name in the global environment
tmp_var_name <- str_c(sample(letters,30,replace = TRUE),collapse = "")
message(tmp_var_name)
assign(tmp_var_name,x,envir = .GlobalEnv)
# Python can read from that global variable
py_run_string(glue("print (r.{tmp_var_name})"))
# finally,delete the variable
remove(list = tmp_var_name,envir = .GlobalEnv)
}
fun(df1)
#> vemcjnbxvnfvbdgushqkjcmtgzwhpu
#> col1 col2
#> 0 123.0 233.0
#> 1 234.0 283.0
解决方法
文档没有帮助,所以我去了源,这让我发现 the internal py_resolve_envir()
function 在问题的示例中将返回 R 全局环境,但并非总是如此。
特别是它的第一部分是
# if an environment has been set,use it
envir <- getOption("reticulate.engine.environment")
if (is.environment(envir))
return(envir)
意味着您可以将环境传递给名为 reticulate.engine.environment
的选项,并且当您尝试在 python 中子集到 r
时,reticulate 将使用它而不是全局环境作为搜索的位置。
因此,你可以写:
set.seed(47L)
df1 <- data.frame(col1 = c(123,234),col2 = c(233,283))
fun <- function(x) {
e <- new.env()
options("reticulate.engine.environment" = e)
# create a new variable with a random name
tmp_var_name <- paste(sample(letters,30,replace = TRUE),collapse = "")
message(tmp_var_name)
assign(tmp_var_name,x,envir = e)
res <- reticulate::py_run_string(glue::glue("print( r.{tmp_var_name} )"))
options("reticulate.engine.environment" = NULL) # unset option
invisible(res)
}
fun(df1)
#> zjtvorkmoydsepnxkabmeondrjaanu
#> col1 col2
#> 0 123.0 233.0
#> 1 234.0 283.0
避免将所有内容都放在全局环境中。
Coda:从 Python 执行任意 R 代码
如果您在 dir()
上调用 r
,它定义了一个 __getitem__
方法,当您执行 r.{tmp_var_name}
时会调用该方法:
reticulate::py_run_string('print( dir(r) )')
#> ['__class__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__getattr__','__getattribute__','__getitem__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__module__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__setitem__','__sizeof__','__str__','__subclasshook__','__weakref__']
reticulate::py_run_string('print( r.__getitem__ )')
#> <bound method make_python_function.<locals>.python_function of <__main__.R object at 0x111b0b6a0>>
x <- 47L
reticulate::py_run_string('print( r.__getitem__("x") )')
#> 47
这个的定义是here。值得注意的是,getter()
调用它的第二个参数 code
是有原因的:
getter <- function(self,code) {
envir <- py_resolve_envir()
object <- eval(parse(text = as_r_value(code)),envir = envir)
r_to_py(object,convert = is.function(object))
}
——它被传递给 eval(parse(text = ...))
,它会将任何字符串转换为 R 代码并运行它。这意味着您可以将任何 R 代码传递给 r.__getitem__()
,但有一个限制,即它必须返回可以转换为 Python 类型(例如,不是环境或模型)的内容,以及更肤浅的限制是它不能包含换行符。但这仍然允许您在 R 中执行任意代码:
reticulate::py_run_string("print( r.__getitem__('head(iris)') )")
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 0 5.1 3.5 1.4 0.2 setosa
#> 1 4.9 3.0 1.4 0.2 setosa
#> 2 4.7 3.2 1.3 0.2 setosa
#> 3 4.6 3.1 1.5 0.2 setosa
#> 4 5.0 3.6 1.4 0.2 setosa
#> 5 5.4 3.9 1.7 0.4 setosa
reticulate::py_run_string("print( r.__getitem__('broom::tidy(lm(mpg ~ hp,mtcars))') )")
#> term estimate std.error statistic p.value
#> 0 (Intercept) 30.098861 1.633921 18.421246 6.642736e-18
#> 1 hp -0.068228 0.010119 -6.742389 1.787835e-07
# this method also gets called if you subset `r` with `[]`:
reticulate::py_run_string("print( r['library(tidyverse); mtcars %>% group_by(cyl) %>% summarise(across(everything(),mean))'] )")
#> cyl mpg disp ... am gear carb
#> 0 4.0 26.663636 105.136364 ... 0.727273 4.090909 1.545455
#> 1 6.0 19.742857 183.314286 ... 0.428571 3.857143 3.428571
#> 2 8.0 15.100000 353.100000 ... 0.142857 3.285714 3.500000
#>
#> [3 rows x 11 columns]
此代码将从 py_resolve_envir()
返回的任何环境中调用,但如果您可以从那里访问(或制作!)您想要的东西,您就可以获取它。
此外,这感觉就像 SQL 注入攻击一样,表明如果您在 Shiny 或类似环境中运行它,您真的不应该让用户选择变量名称,但我不希望那是反正很有可能。
,只需在函数内部进行赋值并使用 list
py$
fun <- function(x) {
# create a new variable with a random name in the global environment
tmp_var_name <- str_c(sample(letters,envir = .GlobalEnv)
# Python can read from that global variable
py_run_string(glue("x1 = r.{tmp_var_name}"))
# finally,delete the variable
remove(list = tmp_var_name,envir = .GlobalEnv)
py$x1
}
-测试
fun(df1)
#galdlxvgjpxuzkvmwznspxjdrftcmu
#$col2
#[1] 233 283
#$col1
#[1] 123 234
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。