我正在尝试编写一个函数来评估代码并存储结果,包括代码中发出的任何可能的条件。我的工作完全正常,除了我的函数(让我们称之为evalcapt
)在错误处理表达式中运行的情况。
问题是withCallingHandlers
将继续寻找匹配的条件处理程序,如果有人在我的函数之外定义了这样的处理程序,我的函数将失去对执行的控制。以下是问题的简化示例:
evalcapt <- function(expr) {
conds <- list()
withCallingHandlers(
val <- eval(expr),
condition=function(e) {
message("Caught condition of class ", deparse(class(e)))
conds <<- c(conds, list(e))
} )
list(val=val, conditions=conds)
}
myCondition <- simpleCondition("this is a custom condition")
class(myCondition) <- c("custom", class(myCondition))
expr <- expression(signalCondition(myCondition), 25)
tryCatch(evalcapt(expr))
按预期工作
Caught condition of class c("custom", "simpleCondition", "condition")
$val
[1] 25
$conditions
$conditions[[1]]
<custom: this is a custom condition>
但:
tryCatch(
evalcapt(expr),
custom=function(e) stop("Hijacked `evalcapt`!")
)
不起作用:
Caught condition of class c("custom", "simpleCondition", "condition")
Error in value[[3L]](cond) : Hijacked `evalcapt`!
我真正需要的是一种在代码中发出条件信号后立即定义重启的方法,坦率地说就是withCallingHandlers
看起来正常工作的方式(当我的处理程序是最后一个可用的处理程序时),但我我在处理函数中browse
时看不到重新启动,并使用computeRestarts
。
tryCatch
tryCatch
与withCallingHandlers
没有相同的问题,因为它在找到第一个处理程序后不会继续查找处理程序。最大的问题是它还没有继续评估条件后的代码。如果您查看上面有效的示例,但tryCatch
为withCallingHandlers
,则不会返回值(25),因为执行将在条件之后返回到tryCatch
帧处理完毕。
所以基本上,我正在寻找tryCatch
和withCallingHandlers
之间的混合,一个将控制权返回给条件信号器,但也会在第一个之后停止寻找更多的处理程序找到。
tryCatch
好的,但是你如何分手(以及更复杂的功能以及遍布信号的条件):
fun <- function(myCondition) {
signalCondition(myCondition)
25
}
expr <- expression(fun())
我查找了与.Internal(.signalCondition())
调用关联的源代码,看看我是否可以弄清楚是否有幕后重启设置,但我不在那里。好像是:
void R_ReturnOrRestart(SEXP val, SEXP env, Rboolean restart)
{
int mask;
RCNTXT *c;
mask = CTXT_BROWSER | CTXT_FUNCTION;
for (c = R_GlobalContext; c; c = c->nextcontext) {
if (c->callflag & mask && c->cloenv == env)
findcontext(mask, env, val);
else if (restart && IS_RESTART_BIT_SET(c->callflag))
findcontext(CTXT_RESTART, c->cloenv, R_RestartToken);
else if (c->callflag == CTXT_TOPLEVEL)
error(_("No function to return from, jumping to top level"));
}
}
来自src / main / errors.c的正在进行一些重启调用,这由do_signalCondition
调用,但我不知道如何搞乱这个。
答案 0 :(得分:5)
我认为您要找的是在发出特殊情况时使用withRestarts
,例如来自warning
:
withRestarts({
.Internal(.signalCondition(cond, message, call))
.Internal(.dfltWarn(message, call))
}, muffleWarning = function() NULL)
所以
evalcapt <- function(expr) {
conds <- list()
withCallingHandlers(
val <- eval(expr),
custom=function(e) {
message("Caught condition of class ", deparse(class(e)))
conds <<- c(conds, list(e))
invokeRestart("muffleCustom")
} )
list(val=val, conditions=conds)
}
expr <- expression(withRestarts({
signalCondition(myCondition)
}, muffleCustom=function() NULL), 25)
导致
> tryCatch(evalcapt(expr))
Caught condition of class c("custom", "simpleCondition", "condition")
$val
[1] 25
$conditions
$conditions[[1]]
<custom: this is a custom condition>
> tryCatch(
+ evalcapt(expr),
+ custom=function(e) stop("Hijacked `evalcapt`!")
+ )
Caught condition of class c("custom", "simpleCondition", "condition")
$val
[1] 25
$conditions
$conditions[[1]]
<custom: this is a custom condition>
答案 1 :(得分:2)
据我所知,并不能解决这个问题(我很高兴被证明是错误的)。如果我们查看tryCatch
和withCallingHandlers
如何注册处理程序,可以看到问题的根源:
.Internal(.addCondHands(name, list(handler), parentenv, environment(), FALSE)) # tryCatch
.Internal(.addCondHands(classes, handlers, parentenv, NULL, TRUE)) # withCallingHandlers
关键点是最后一个参数,tryCatch
中为FALSE,withCallingHandlers
中为TRUE。此参数导致do_addCondHands
&gt; gp
bit设置{{3}} src / main / errors.c中的mkHandlerEntry
。
当发出条件信号时,do_signalCondition
(仍在src / main / errors.c中)会查询相同的位:
// simplified code excerpt from `do_signalCondition
PROTECT(oldstack = R_HandlerStack);
while ((list = findConditionHandler(cond)) != R_NilValue) {
SEXP entry = CAR(list);
R_HandlerStack = CDR(list);
if (IS_CALLING_ENTRY(entry)) { // <<------------- Consult GP bit
... // Evaluate handler
} else gotoExitingHandler(cond, ecall, entry); // Evaluate handler and exit
}
R_HandlerStack = oldstack;
return R_NilValue;
基本上,如果设置了GP位,那么我们会评估处理程序,并继续迭代处理程序堆栈。如果没有设置,那么我们运行运行处理程序的gotExitingHandler
,然后将控制返回到处理控制结构,而不是恢复发出条件信号的代码。
由于GP位只能告诉您执行以下两种操作之一,因此没有直接的方法来修改此调用的行为(即,如果使用withCallingHandlers
,则要么遍历所有处理程序,要么停止在由tryCatch
注册的第一个匹配的那个。
我玩弄了trace
signalConditions
{{1}}的想法,在那里添加了一个重启,但这似乎过于苛刻。
答案 2 :(得分:1)
使用一点C,您可以评估ToplevelExec()
中的表达式,以将其与堆栈中注册的所有处理程序隔离开来。
我们将在下一个rlang版本中将它暴露在R级别。
答案 3 :(得分:0)
我可能会迟到一些,但是我也一直在研究条件系统,我想我已经找到了其他解决方案。
但是首先:这一定是一个难题的一些原因,而不是通常可以轻松解决的问题。
问题是哪个函数正在发出条件信号,并且如果该函数引发条件,该函数是否可以继续执行。错误也被实现为“只是一个条件”,但是大多数函数在抛出stop()之后都不希望继续执行。
而且某些功能可能会通过某种条件,希望不会再被它困扰。
通常,这意味着只有在函数明确表示可以接受时,才可以在停止后返回控件:提供重新启动。
可能还有其他严重的情况可以发出信号,如果函数希望总是捕获到这种情况,而您强迫它返回执行,则情况将严重恶化。
当您按照以下方式编写并恢复执行该怎么办?
myfun <- function(deleteFiles=NULL) {
if (!all(haveRights(deleteFiles))) stop("Access denied")
file.remove(deleteFiles)
}
withCallingHandlers(val <- eval(myfun(myProtectedFiles)),
error=function(e) message("I'm just going to ignore everything..."))
如果没有调用其他处理程序(警告用户已停止调用),则即使该函数对此有(小的)防护措施,文件也将被删除。 在发生错误的情况下,这很明显,但在其他情况下也可能会发生情况,因此,我认为这是如果您停止传递条件,则R并不真正支持它的主要原因,除非这意味着停止。 >
尽管如此,我认为我发现了两种解决您的问题的方法。 首先是简单地逐步执行 expr ,这与Martin Morgans解决方案非常接近,但是将withRestarts移入了函数中:
evalcapt <- function(expr) {
conds <- list()
for (i in seq_along(expr)) {
withCallingHandlers(
val <- withRestarts(
eval(expr[[i]]),
muffleCustom = function()
NULL
),
custom = function(e) {
message("Caught condition of class ", deparse(class(e)))
conds <<- c(conds, list(e))
invokeRestart(findRestart("muffleCustom"))
})
}
list(val = val, conditions = conds)
}
主要缺点是它不能挖掘功能, expr 会在调用的每个级别上执行。
因此,如果调用evalcapt(myfun()),则for循环会将其视为一条指令。而这一条指令引发了一个条件->因此不会返回->因此,如果您没有捕获任何内容,就看不到任何输出。
OTOH,evalcapt(expression(signalCondition(myCondition),25))按要求工作,因为这是一个包含2个元素的表达式,每个元素都被调用。
如果您想成为铁杆,我认为您可以尝试逐步评估myfun(),但始终存在您想要深入的问题。如果myfun()调用了myotherfun(),又调用了myotherotherfun(),您是否想将控制权返回到myfun失败,myotherfun或myotherotherfun失败的地步?
基本上,这只是您要停止执行的级别以及要在何处恢复的猜测。
第二种解决方案:劫持对signalCondition的所有调用。这意味着您可能会进入一个很深的层次,尽管不是最深层次的层次(没有基元或调用.signalCondition的代码)。
我认为,如果您真的确定仅由您编写的代码抛出自定义条件,这将是最好的方法:这意味着执行直接在signalCondition之后执行。
这给了我这个功能:
evalcapt <- function(expr) {
if(exists('conds', parent.frame(), inherits=FALSE)) {
conds_backup <- get('conds', parent.frame(), inherits=FALSE)
on.exit(assign('conds', conds_backup, parent.frame(), inherits=FALSE), add=TRUE)
} else {
on.exit(rm('conds', pos=parent.frame(), inherits=FALSE), add=TRUE)
}
assign('conds', list(), parent.frame(), inherits=FALSE)
origsignalCondition <- signalCondition
if(exists('signalCondition', parent.frame(), inherits=FALSE)) {
signal_backup <- get('signalCondition', parent.frame(), inherits=FALSE)
on.exit(assign('signalCondition', signal_backup, parent.frame(), inherits=FALSE), add=TRUE)
} else {
on.exit(rm('signalCondition', pos=parent.frame(), inherits=FALSE), add=TRUE)
}
assign('signalCondition', function(e) {
if(is(e, 'custom')) {
message("Caught condition of class ", deparse(class(e)))
conds <<- c(conds, list(e))
} else {
origsignalCondition(e)
}
}, parent.frame())
val <- eval(expr, parent.frame())
list(val=val, conditions=conds)
}
它看起来更混乱,但这主要是因为使用哪种环境存在更多问题。区别在于,在这里,我将调用环境用作上下文,并劫持了也需要存在的signalCondition()。然后我们需要清理。
但是主要用途是覆盖signalCondition:如果看到自定义错误,则将其记录下来并返回控制。如果是其他情况,我们会继续控制。
这里可能存在一些较小的缺点: