使用substitute()获取参数名称,多层级向上

6

考虑这个函数a(),它会打印出传入的参数:

a <- function(x) {
  message("The input is ", deparse(substitute(x)))
}

a("foo")
# The input is "foo"

tmplist <- list(x1 = 1, x2=2)
a(tmplist)
# The input is tmplist

这个方法可以运行。但是当a()从另一个函数中调用时,它不会再打印出原始的参数名称:

b <- function(y) {
  a(y)
}

b("foo")
# The input is y

b(tmplist)
# The input is y

似乎有效的一种解决方案是在另一个substituteeval中包裹它:

a1 <- function(x) {
  message("The input is ", deparse(eval(substitute(substitute(x)), parent.frame())))
}

a1("foo")
# The input is "foo"

tmplist <- list(x1 = 1, x2=2)
a1(tmplist)
# The input is tmplist

b1 <- function(y) {
  a1(y)
}

b1("foo")
# The input is "foo"

b1(tmplist)
# The input is tmplist

但这种方法似乎不够优雅。而且如果我添加了另一层,它就会失效:

c1 <- function(z) {
  b1(z)
}
c1("foo")
# The input is z

有没有一种好的、普遍的方法来获取原始参数?


我不是环境处理方面的专家,但我认为要么像kohske那样玩弄parent.frame,要么指定一个全局变量是你唯一的选择。R不像C那样通过引用传递。 - Carl Witthoft
4个回答

3

我不确定这种方法在所有情况下都有效,但是你可以尝试以下步骤:

f0 <- function(x) {
  nn <- substitute(x)
  i <- 1
  while(TRUE) {
    on <- do.call("substitute", list(as.name(nn), parent.frame(i)))
    if (on == nn) break;
    nn <- on
    i <- i + 1
  }
  message("The input is ", nn)
}

f1 <-function(.f1) f0(.f1)
f2 <- function(.f2) f1(.f2)

然后,
> f2(foo)
The input is foo
> f1(poo)
The input is poo
> f0(moo)
The input is moo
> f2(";(")
The input is ;(
> f1(":)")
The input is :)
> f0(":p")
The input is :p

这很不错。但是如果变量的名称在连续的两个步骤中相同,那么我认为它会被愚弄成在真正完成之前停止。假设您定义了f1和f2,使它们以“xx”作为参数--然后它就停在那里了。也许解决方案是保持循环直到“i==sys.nframe()”。 - wch

3

参考kohske的回答,这里有一个可行的方案,但如果变量在连续两个帧中具有相同的名称,则不会过早地停止堆栈递归。我不知道它是否适用于所有情况,但它似乎满足我的需求。字符串与变量的引用方式略有不同,但对于我的情况来说是可以接受的。

a <- function(x) {
  newname <- substitute(x)

  # Travel up the frame stack until we hit the top.
  for(i in seq_len(sys.nframe())) {
    oldname <- do.call("substitute", list(as.name(newname), parent.frame(i)))
    newname <- oldname
  }
  message("The input is ", deparse(newname))
}

b <- function(y)  a(y)

c <- function(z)  b(z)

a("adsf")
# The input is adsf
a(foo)
# The input is foo

b("adsf")
# The input is adsf
b(foo)
# The input is foo

c("adsf")
# The input is adsf
c(foo)
# The input is foo

1

虽然这本身是一个有趣的问题,但我想最好的解决方案是否只是将变量名作为字符传递,即用引号括起来。然后就不需要做任何额外的操作了。如果需要与名称对应的对象,则可以使用getas.namedo.call获取,具体取决于您在函数内部如何使用它。

> f0 <- function(x) {message("The input is ", x)}
> f1 <- function(.f1) f0(.f1)
> f2 <- function(.f2) f1(.f2)
> f2("aa")
The input is aa
> f1("bb")
The input is bb
> f0("cc")
The input is cc

不幸的是,这种方法在我的应用程序中行不通。我需要传递原始对象。所有这些的目的只是在出现问题时打印出输入参数的名称和错误消息。 - wch
嗯......我相信有一种方法可以将错误“传递”到上层,这样调用函数中的错误将会在顶层函数中触发消息,您在那里可以知道变量的“真实名称”。比我更熟悉errortrycatch使用的人可能能够介入。再次看到了提出正确问题的重要性! - Carl Witthoft
1
@wch:进一步解释卡尔的最后评论,正确的问题通常是关于你实际想要做什么,而不是关于你如何去做。也就是说,不是“我该如何使用X来做Y?”或者更糟糕的是,“我该如何做X?”相反,应该问:“我该如何做Y?我已经尝试了X,但它没有起作用。” - Aaron left Stack Overflow

0

当你需要递归调用函数时,怎么样?

deparse(substitute(x))!=deparse(eval(substitute(substitute(x)), parent.frame())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接