Имя функции текущего журнала
У меня есть несколько пользовательских логических функций, которые являются расширениями 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
начинается с имени вызываемой функции. Очень аккуратно, пока я не начну реорганизовывать свой код и переименовывать функции и т.д.
Благодаря старому списку R-списка от Брайана Рипли, если я не ошибаюсь, я нашел этот код для получения "текущего имени функции":
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]])))
}
}
- > для этих случаев, по-видимому (и понятно) мне нужно n = 3 в вызове sys.parent
в моей функции catw
.
- Я иногда использую
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))
Ответы
Ответ 1
РЕДАКТИРОВАТЬ: Завершить переписывание функции
В новой версии этой функции используется стек вызовов 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
Ответ 2
Я думал, что добавлю прогресс, достигнутый до сих пор, полностью основанный на работе Андри. Довольно уверен, что другим людям это понравится, так что теперь это часть пакета, который я разрабатываю (не на 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)
}
Как уже упоминалось в комментариях к Андри, до сих пор все еще есть некоторые проблемы относительно do.call
. На данный момент я собираюсь прекратить тратить время, но разместил связанный с ним вопрос в r-devel mailinglist. Если/когда я получаю ответ там, и он можно использовать, я обновлю функции.