Python 和 R 之间的互操作性
interoperability between Python and 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
只需在函数内部做一个赋值,然后用py$
提取list
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("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
文档没有帮助,所以我找到了源代码,这让我发现 the internal py_resolve_envir()
function 在问题的示例中 return R 全局环境,但不会'总是。
特别是,它的第一部分是
# if an environment has been set, use it
envir <- getOption("reticulate.engine.environment")
if (is.environment(envir))
return(envir)
意味着您可以将环境传递给名为 reticulate.engine.environment
的选项,当您尝试子集化到 r
时,网状结构将使用它而不是全局环境作为搜索的位置 python.
因此,你可以这样写:
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 代码
如果您在 r
上调用 dir()
,它会定义一个 __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__()
,但有一个限制,即它必须 return 可以转换为 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()
returns 调用,但如果您可以从那里访问(或制作!)您想要的东西,您可以获取它。
此外,这感觉很像 SQL 注入攻击,表明您 真的 不应该让用户选择变量名,如果您是 运行在 Shiny 或类似版本中使用它,但我认为这不太可能。
从 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
只需在函数内部做一个赋值,然后用py$
list
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("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
文档没有帮助,所以我找到了源代码,这让我发现 the internal py_resolve_envir()
function 在问题的示例中 return R 全局环境,但不会'总是。
特别是,它的第一部分是
# if an environment has been set, use it
envir <- getOption("reticulate.engine.environment")
if (is.environment(envir))
return(envir)
意味着您可以将环境传递给名为 reticulate.engine.environment
的选项,当您尝试子集化到 r
时,网状结构将使用它而不是全局环境作为搜索的位置 python.
因此,你可以这样写:
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 代码如果您在 r
上调用 dir()
,它会定义一个 __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__()
,但有一个限制,即它必须 return 可以转换为 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()
returns 调用,但如果您可以从那里访问(或制作!)您想要的东西,您可以获取它。
此外,这感觉很像 SQL 注入攻击,表明您 真的 不应该让用户选择变量名,如果您是 运行在 Shiny 或类似版本中使用它,但我认为这不太可能。