我试图在 R(例如,a, *b, c = [1,2,3], "C"
)中使 Python 3 样式的赋值解包成为可能,尽管我已经如此接近(你可以在这里查看我的代码),但我最终遇到了一些(奇怪的)问题。
我的代码是这样工作的:
a %,*% b %,% c <- c(1,2,3,4,5)
并将分配a
= 1
,b
=c(2,3,4)
和c
= 5
(我的代码实际上确实这样做了,但是我稍后会遇到一个小障碍)。
为了让它做任何事情,我必须定义:
`%,%` <- function(lhs, rhs) {
...
}
和
`%,%<-` <- function(lhs, rhs, value) {
...
}
(以及%,*%
和%,*%<-
,它们是先前函数的轻微变体)。
第一个问题:为什么用 R*tmp*
代替lhs
论点
据我所知,R 首先从左到右评估此代码(即,从a
to c
,直到它到达最后一个%,%
,然后从右到左返回,沿途分配值。但第一个我注意到的奇怪的事情是,当我做match.call()
或substitute(lhs)
类似的事情时x %infix% y <- z
,它说lhs
参数的输入%infix%
是*tmp*
,而不是说,a
或x
。
这对我来说很奇怪,我在 R 手册或文档中找不到任何提及。我实际上在我的代码中使用了这个奇怪的约定(即,它没有在赋值的右侧显示这种行为,所以我可以使用*tmp*
输入的存在来使%,%
赋值的这一侧表现不同),但我不知道为什么会这样。
第二个问题:为什么 R先检查对象是否存在
我的第二个问题是使我的代码最终无法正常工作的原因。我注意到,如果您从任何赋值左侧的变量名开始,R 似乎甚至不会开始计算表达式——它会返回错误object '<variable name>' not found
。即,如果x
未定义,x %infix% y <- z
则不会评估,即使%infix%
实际上并未使用或评估x
。
为什么 R 会有这样的行为,我可以改变它或绕过它吗? 如果我可以在R 检查是否存在%,%
之前x
运行代码,我可能会破解它,这样我就不会成为问题,并且我的 Python 解包代码将非常有用,可以实际共享。但就像现在一样,第一个变量需要已经存在,我认为这太局限了。我知道我可以通过将 更改<-
为自定义中缀运算符来做一些事情%<-%
,但是我的代码将与zeallot
包非常相似,以至于我认为它不值得。(它的功能已经非常接近了,但我更喜欢我的风格。)
编辑:
遵循 Ben Bolker 的出色建议,我能够找到解决问题的方法......通过覆盖<-
.
`<-` <- function(x, value) {
base::`<-`(`=`, base::`=`)
find_and_assign(match.call(), parent.frame())
do.call(base::`<-`, list(x = substitute(x), value = substitute(value)),
quote = FALSE, envir = parent.frame())
}
find_and_assign <- function(expr, envir) {
base::`<-`(`<-`, base::`<-`)
base::`<-`(`=`, base::`=`)
while (is.call(expr)) expr <- expr[[2]]
if (!rlang::is_symbol(expr)) return()
var <- rlang::as_string(expr) # A little safer than `as.character()`
if (!exists(var, envir = envir)) {
assign(var, NULL, envir = envir)
}
}
我很确定这将是一个致命的罪过,对吧?我无法确切地看到它会如何搞砸任何事情,但我的程序员感觉的刺痛告诉我这不适合分享像一个包这样的东西......这会有多糟糕?