在警告消息中显示父函数的名称

Show the name of the parent function in the warning message

提问人:Karolis Koncevičius 提问时间:4/3/2023 更新时间:4/5/2023 访问量:96

问:

我有一个用于显示警告的辅助函数:

mywarn <- function() {
  warning("I warn you")
}

如果我直接调用该函数,我将获得发生错误的函数的名称:

mywarn()
> Warning message:
  In mywarn() : I warn you

但是,我想从其他几个函数中使用警告函数:

myfun <- function(x) {
  x <- x^2
  mywarn()
  x
}

但是当显示警告时,它仍然显示我的警告函数的名称:

myfun(10)
> Warning message:
  In mywarn() : I warn you

显示警告消息的最佳方法是什么?myfunmywarn

R 警告

评论

0赞 user2554330 4/3/2023
我已经将我的解决方案编辑为似乎效果很好的东西。

答:

5赞 Konrad Rudolph 4/3/2023 #1

最简单的方法是使用合适的调用对象显式构造一个条件对象,并将其传递给 。这将覆盖使用调用函数的默认逻辑。warningwarning

mywarn <- function (call = sys.call(sys.parent())) {
  warning(simpleWarning("I warn you", call))
}
myfun(10)
Warning message:
In myfun(10) : I warn you

评论

0赞 Karolis Koncevičius 4/3/2023
很高兴看到如此快速的回复 - 所以首先 - 谢谢!这种方法的问题似乎在于,例如,它会分解:sapply(10, myfun)
1赞 Konrad Rudolph 4/3/2023
@KarolisKoncevičius 这是实施方式的结果。直接呼叫将具有相同的行为。这是 R 中高级函数的一般特征,实际上没有好办法解决这个问题。但是,这就是为什么我的函数定义为参数,因此调用方可以自定义它。如果知道它将从 调用,则可以提供自己的对象 ,例如 。sapplywarningmywarncallmyfunsapplycallmywarnmywarn(call("myfun", x))
0赞 Karolis Koncevičius 4/3/2023
非常好的观点,直接调用会遇到与 相同的问题。但我很难看出问题是什么。简单来说,“总是显示给我打电话的直系父母的名字”听起来很容易。使用 myfun -> mywarn 和 sapply -> myfun -> mywarn 的直接父级似乎是一样的。或者你认为我在这里遗漏了一些隐藏的复杂性?warning()sapply
2赞 Konrad Rudolph 4/3/2023
@KarolisKoncevičius 你缺少的是 R 中的函数本质上没有名称。它们只是生活在记忆中某个地方的物体。您可以将它们绑定到名称,但没有 1 对 1 映射。当你将一个函数传递给它时,会得到一个不同的名称(参数的名称:)。特别是,请考虑在使用匿名函数时要打印的内容:.sapplyFUNsapply(10, \(x) { mywarn(); x ^2 })
0赞 Karolis Koncevičius 4/3/2023
谢谢你的解释。是的,这现在似乎很明显,在得到一个参数的名称。sapply()myfunFUN
1赞 Stéphane Laurent 4/3/2023 #2

或:

warnme <- function() {
  call <- deparse(sys.calls()[[sys.nframe()-1]])
  warnmessage <- sprintf("Warning in %s", call)
  warning(warnmessage, call. = FALSE)
}

f <- function(x) {
  warnme()
}

f(2)

评论

0赞 Karolis Koncevičius 4/3/2023
您好,感谢您的快速回复。我自己尝试过类似的事情,这里的问题是,当这样调用时,它似乎会崩溃:sapply(2, f)
2赞 user2554330 4/3/2023 #3

在评论中,您指出,针对您的问题的其他解决方案在以下情况下都失败了

sapply(10, myfun) 

他们显示类似的东西

Warning message:
In FUN(X[[i]], ...) : I warn you

我认为如果没有调试信息,这几乎是不可避免的。在调用中,是一个对象; 不在乎它的名字。因此,要将“myfun”放入消息中而不是实现中的局部变量,您需要识别它是从 调用的,并进一步查找调用堆栈以查找被调用的函数的名称。除此之外,还有很多其他功能需要处理,所以这不是一个可行的方法。sapply()myfunsapplysapplymywarnsapplysapply

如果你碰巧知道这将是一个问题,你可以通过做更多的工作来解决它。它可以将自己的名称作为消息的一部分传递,而不是简单地使用错误消息进行调用。这可能很简单myfunmywarn

mywarn("myfun")

或更精细的喜欢

mywarn(call = substitute(myfun(x),list(x = substitute(x))))

(其中一些丑陋可能会被移入,但的存在是必不可少的)。mywarnmyfun(x)

如果在源中启用了调试信息,则可以执行更多操作。如果您使用 , 但对于包源代码来说,它是可选的。source()

例如,如果将以下代码放在名为 的文件中,然后调用 ,则将获得如下所示的输出:test.Rsource("test.R")

myfun <- function(x) {
  x <- x^2
  mywarn()
  x
}

mywarn <- function (msg = "I warn you", call = sys.call(sys.parent()))
{   
  calls <- sys.calls()
  for (i in rev(seq_along(calls))) {
    location <- getSrcref(calls[[i]])
    if (!is.null(location)) {
      call <- NULL
      filename <- getSrcFilename(location)
      linenum  <- getSrcLocation(location)
      functionname <- findLineNum(attr(location, "srcfile"), linenum)
      if (length(functionname) < 1) prefix <- "At "
      else prefix <- paste0("In ", functionname[[1]]$name, "() at ")
      msg <- sprintf("%s%s#%d: %s", prefix, basename(filename), linenum, msg)
    }
  }
  warning(simpleWarning(msg, call))
}

sapply(10, myfun)
Warning message:
At test.R#25: In myfun() at test.R#3: I warn you  

这表示源文件第 25 行调用了最终到达源文件第 3 行并发出警告的内容。您可以根据自己的喜好使前缀或多或少地提供信息;例如,我可能只包括最近的位置 ()。myfun()In myfun() at test.R#3:

如果在调试信息不存在的情况下工作,它将回退到 @KonradRudolph 的解决方案。

评论

0赞 Konrad Rudolph 4/3/2023
丑陋可以通过写作来避免mywarn(call("myfun", x)) ;-)
0赞 Konrad Rudolph 4/3/2023
顺便说一句,sapply 可以避免这个问题,但它会使其实现变得更加复杂。实际上,我一半期望这些函数表现得更好,但它们的行为完全相同。改进这将是一个很好的项目。purrr::map*
0赞 Karolis Koncevičius 4/4/2023
在收到的所有答案中,这似乎是迄今为止最有道理的。我不喜欢手动传递函数名称,但这可能是不可避免的。你的第二次尝试对我的口味来说有点太复杂了,但也恰到好处。我会等几天,也许其他人会有一些其他想法,如果没有 - 会很乐意接受这一点。
1赞 Merijn van Tilborg 4/3/2023 #4

更新

仍然不是 100% 满足 OP 的要求,但也许这会有所帮助。显示触发警告的函数调用的警告,并添加回溯以向下跟踪函数,直到调用警告函数。

mywarn <- function () {
  msg <- sys.calls()
  warning(
    sprintf("In %s : I warn you\n", gsub("[[:space:]]", "", msg[1][1], perl = TRUE)),
    "Traceback : \n", 
    sapply(msg[2:length(msg)], \(x) sprintf("\t%s\n", deparse(x)))
  , call. = F)
}

mywrap <- function(x) myfun(x); mywrap(2)

# Warning message:
#   In mywrap(2) : I warn you
# Traceback : 
#   myfun(x)
#   mywarn()

原始答案

不是 100% 完美,但这是我最接近处理所有 3 种情况的方法

mywarn <- function () {
  msg <- deparse(sys.calls()[[1]])[[1]]
  warning(sprintf("In %s : I warn you", msg), call. = FALSE)
}


myfun <- function(x) {
  x <- x^2
  mywarn()
  x
}

myfun(10)

# Warning message:
# In myfun(10) : I warn you 

sapply(10, myfun)

# Warning message:
# In sapply(10, myfun) : I warn you 

sapply(10, \(x) { mywarn(); x ^2 })

# Warning message:
# In sapply(10, function(x) { : I warn you 

评论

0赞 Konrad Rudolph 4/3/2023
当调用本身嵌套在另一个函数的更深处而不是在顶层调用时,此解决方案将不再显示有意义的结果。sapply
0赞 Karolis Koncevičius 4/4/2023
你好,似乎是一个不错的尝试,但是,除了@KonradRudolph所说的之外,一个简单的包装器也会失败: - “mywrap(2) 中的警告消息” ...mywrap <- function(x) myfun(x); mywrap(2)
0赞 Merijn van Tilborg 4/5/2023
我同意它并不完美,我更新了我的答案,但仍然不完美,但它显示了父函数触发警告,并回溯以“遵循”错误函数的路径。