我有一些自定义日志功能是cat
的扩展名。一个基本的例子是这样的:
catt<-function(..., file = "", sep = " ", fill = FALSE, labels = NULL,
append = FALSE)
{
cat(..., format(Sys.time(), "(%Y-%m-%d %H:%M:%S)"), "\n", file = file,
sep = sep, fill = fill, labels = labels, append = append)
}
现在,我使用(自制)函数做了很多工作,并使用其中一些logfuntions来查看进度,这非常有效。但我注意到,我几乎总是使用这样的函数:
somefunc<-function(blabla)
{
catt("somefunc: start")
#do some very useful stuff here
catt("somefunc: some time later")
#even more useful stuff
catt("somefunc: the end")
}
注意每次对catt
的调用是如何从调用函数的名称开始的。非常整洁,直到我开始重构我的代码并重命名函数等。
感谢Brian Ripley的一些旧R-list帖子,如果我没弄错,我发现这段代码可以获得'当前的函数名':
catw<-function(..., file = "", sep = " ", fill = FALSE, labels = NULL,
append = FALSE)
{
curcall<-sys.call(sys.parent(n=1))
prefix<-paste(match.call(call=curcall)[[1]], ":", sep="")
cat(prefix, ..., format(Sys.time(), "(%Y-%m-%d %H:%M:%S)"), "\n",
file = file, sep = sep, fill = fill, labels = labels, append = append)
}
这非常好,但并不总是有效,因为:
lapply
中使用的匿名函数
功能类型,如下所示:aFunc<-function(somedataframe) { result<-lapply(seq_along(somedataframe), function(i){ catw("working on col", i, "/", ncol(somedataframe)) #do some more stuff here and return something return(sum(is.na(somedataframe[[i]]))) } }
- &GT;对于这些情况,显然(并且可以理解)我在sys.parent
函数的catw
调用中需要n = 3。
do.call
:它似乎是我当前的实现
也不起作用(再一次,我可以稍微理解它
我还没弄明白。所以,我的问题是:有没有办法在callstack中找到第一个命名的函数(跳过日志函数本身,也许还有其他一些“众所周知的”异常),这将允许我为所有情况编写一个catw
版本(这样我可以愉快地重构而不用担心我的日志代码)?你会怎么做这样的事情?
修改:应支持这些案例:
testa<-function(par1)
{
catw("Hello from testa, par1=", par1)
for(i in 1:2) catw("normal loop from testa, item", i)
rv<-sapply(1:2, function(i){catw("sapply from testa, item", i);return(i)})
return(rv)
}
testb<-function(par1, par2)
{
catw("Hello from testb, par1=", par1)
for(i in 1:2) catw("normal loop from testb, item", i)
rv<-sapply(1:2, function(i){catw("sapply from testb, item", i);return(i)})
catw("Will now call testa from testb")
rv2<-testa(par1)
catw("Back from testa call in testb")
catw("Will now do.call testa from testb")
rv2<-do.call(testa, list(par1))
catw("Back from testa do.call in testb")
return(list(rv, rv2))
}
testa(123)
testb(123,456)
do.call(testb, list(123,456))
答案 0 :(得分:14)
编辑:完全重写功能
此函数的新版本使用调用堆栈sys.calls()
,而不是match.call
。
调用堆栈包含完整的调用函数。所以现在的诀窍是只提取你真正想要的那些部分。我已经在clean_cs
函数中进行了一些手动清理。这将计算调用堆栈中的第一个单词,并返回少量已知边缘情况的所需参数,尤其是lapply
,sapply
和do.call
。
这种方法的唯一缺点是它会将函数名一直返回到调用堆栈的顶部。也许合乎逻辑的下一步是将这些函数与特定的环境/命名空间进行比较,并根据该函数名称包含/排除函数名称......
我会在这里停下来。它回答了问题中的用例。
新功能:
catw <- function(..., callstack=sys.calls()){
cs <- callstack
cs <- clean_cs(cs)
#browser()
message(paste(cs, ...))
}
clean_cs <- function(x){
val <- sapply(x, function(xt){
z <- strsplit(paste(xt, collapse="\t"), "\t")[[1]]
switch(z[1],
"lapply" = z[3],
"sapply" = z[3],
"do.call" = z[2],
"function" = "FUN",
"source" = "###",
"eval.with.vis" = "###",
z[1]
)
})
val[grepl("\\<function\\>", val)] <- "FUN"
val <- val[!grepl("(###|FUN)", val)]
val <- head(val, -1)
paste(val, collapse="|")
}
测试结果:
testa Hello from testa, par1= 123
testa normal loop from testa, item 1
testa normal loop from testa, item 2
testa sapply from testa, item 1
testa sapply from testa, item 2
testb Hello from testb, par1= 123
testb normal loop from testb, item 1
testb normal loop from testb, item 2
testb sapply from testb, item 1
testb sapply from testb, item 2
testb Will now call testa from testb
testb|testa Hello from testa, par1= 123
testb|testa normal loop from testa, item 1
testb|testa normal loop from testa, item 2
testb|testa sapply from testa, item 1
testb|testa sapply from testa, item 2
testb Back from testa call in testb
testb Will now do.call testa from testb
testb|testa Hello from testa, par1= 123
testb|testa normal loop from testa, item 1
testb|testa normal loop from testa, item 2
testb|testa sapply from testa, item 1
testb|testa sapply from testa, item 2
testb Back from testa do.call in testb
testb Hello from testb, par1= 123
testb normal loop from testb, item 1
testb normal loop from testb, item 2
testb sapply from testb, item 1
testb sapply from testb, item 2
testb Will now call testa from testb
testb|testa Hello from testa, par1= 123
testb|testa normal loop from testa, item 1
testb|testa normal loop from testa, item 2
testb|testa sapply from testa, item 1
testb|testa sapply from testa, item 2
testb Back from testa call in testb
testb Will now do.call testa from testb
testb|testa Hello from testa, par1= 123
testb|testa normal loop from testa, item 1
testb|testa normal loop from testa, item 2
testb|testa sapply from testa, item 1
testb|testa sapply from testa, item 2
testb Back from testa do.call in testb
答案 1 :(得分:4)
我以为我会在Andrie的工作中基于完全添加到目前为止所取得的进展。很确定其他人会喜欢这个,所以它现在是我正在开发的一个软件包的一部分(不是CRAN
而是R-Forge
现在),名为addendum
(包括文档)每晚建设。
在callstack上找到'当前最低命名函数'的函数,带有一些铃声和口哨声:
curfnfinder<-function(skipframes=0, skipnames="(FUN)|(.+apply)|(replicate)",
retIfNone="Not in function", retStack=FALSE, extraPrefPerLevel="\t")
{
prefix<-sapply(3 + skipframes+1:sys.nframe(), function(i){
currv<-sys.call(sys.parent(n=i))[[1]]
return(currv)
})
prefix[grep(skipnames, prefix)] <- NULL
prefix<-gsub("function \\(.*", "do.call", prefix)
if(length(prefix)==0)
{
return(retIfNone)
}
else if(retStack)
{
return(paste(rev(prefix), collapse = "|"))
}
else
{
retval<-as.character(unlist(prefix[1]))
if(length(prefix) > 1)
{
retval<-paste(paste(rep(extraPrefPerLevel, length(prefix) - 1), collapse=""), retval, sep="")
}
return(retval)
}
}
这可以在这样的日志记录功能中使用:
catw<-function(..., file = "", sep = " ", fill = FALSE, labels = NULL,
append = FALSE, prefix=0)
{
if(is.numeric(prefix))
{
prefix<-curfnfinder(skipframes=prefix+1) #note: the +1 is there to avoid returning catw itself
prefix<-paste(prefix, ":", sep="")
}
cat(prefix, ..., format(Sys.time(), "(%Y-%m-%d %H:%M:%S)"), "\n",
file = file, sep = sep, fill = fill, labels = labels, append = append)
}
正如Andrie迄今为止对答案的评论所述,do.call
仍存在一些问题。我现在要停止花时间,但已在r-devel mailinglist上发布相关问题。如果/当我在那里得到响应并且它可用时,我将更新函数。