Регистрация текущего имени функции - PullRequest
16 голосов
/ 05 сентября 2011

У меня есть несколько пользовательских функций журнала, которые являются расширениями 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)
}

Теперь я много работаю с (самодельными) функциями и использую некоторые из этих функций журнала, чтобы увидеть прогресс, который работает довольно хорошо.Однако я замечаю, что я почти всегда использую эти функции следующим образом:

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 начинается с имени функции, из которой он вызывается.Очень аккуратно, пока я не начну реорганизовывать свой код и переименовывать функции и т.д.':

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: похоже, моя текущая реализация тоже не работает (еще раз я могу это понять, хотя и не до конца разобрался.

Итак, мой вопрос: есть ли способ найти первую функцию с именем выше в стеке вызовов (пропуская саму функцию регистрации и, возможно, некоторые другие "хорошо известные" исключения), чтопозвольте мне написать одну версию catw fили во всех случаях (чтобы я мог успешно провести рефакторинг, не беспокоясь о своем коде регистрации)?Как бы вы поступили примерно так?

Редактировать : эти случаи должны поддерживаться:

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))

Ответы [ 2 ]

14 голосов
/ 05 сентября 2011

РЕДАКТИРОВАТЬ: полное переписывание функции

В новой версии этой функции используется стек вызовов 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
4 голосов
/ 06 сентября 2011

Я думал, что добавлю достигнутый прогресс, основываясь полностью на работе Андри.Я уверен, что это понравится другим, так что теперь это часть пакета, который я разрабатываю (не на CRAN, а на R-Forge сейчас), который называется addendum (включая документацию) после ночной сборки.*

Функция для поиска «текущей функции с наименьшим именем» в стеке вызовов с некоторыми прибамбасами:

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)
    }
}

Это может использоваться в такой функции регистрации:1013 * Как уже упоминалось в комментариях к ответу Андри, до сих пор есть некоторые проблемы, связанные с do.call.Сейчас я собираюсь прекратить тратить на это время, но разместил соответствующий вопрос в r-devel рассылке .Если / когда я получу ответ и его можно будет использовать, я обновлю функции.

...