¿Cómo obtener el número de línea de una llamada de función en R?

8

Para fines de depuración, quiero imprimir un número de línea (y el nombre de la función) desde el lugar desde el que se llamó a la función actual. ¿Cómo consigo esto en R?

He visto una solución para obtener el nombre del archivo fuente ¿Pero cómo obtener el número de línea y el nombre de la función?]

EDITAR: Encontré cómo obtener estos datos de traceback()alguna forma, el rastreo puede imprimirlo, pero no estoy seguro de cómo decodificar la información:

f <- function () {
    traceback(x = 3, max.lines = 1)
}

g <- function()
{
    f()
}

x <- g()

source("file.R") # file with this code
# 5: g() at file.R#20
# 4: eval(ei, envir)
# 3: eval(ei, envir)
# 2: withVisible(eval(ei, envir))
# 1: source("file.R")

str(x[[1]])
# chr "g()"
# - attr(*, "srcref")= 'srcref' int [1:8] 20 1 20 8 1 8 20 20
#  ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment:  0x0000000013a31700> 
TMS
fuente

Respuestas:

4

¡Encontré una solución! Lo obtuve del código de traceback ():

f <- function ()
{
    x <- .traceback(x = 1)

    srcloc <- if (!is.null(srcref <- attr(x[[1]], "srcref"))) {
        srcfile <- attr(srcref, "srcfile")
        paste0("Called from ", x[[2]], ", at ", basename(srcfile$filename), "#", srcref[1])
    }

    cat(srcloc, "\n")
}

g <- function()
{
    f()
}

g()
# Called from g(), at file.R#15

Escribió una buena función de contenedor para ello:

# returns a list, unless fmtstring is specified
# level: 1 - caller of the caller of this function; 2 - its parent, 3 - its grand-parent etc.
# fmtstring: return format string: %f (function), %s (source file), %l (line)
# 
# example: str <- caller_info("Called from %f at %s#%l\n")
# !!! it won't work with e.g. cat(caller_info("Called from %f at %s#%l\n"))
# or cat(paste0(caller_info("Called from %f at %s#%l\n"))) !!!
caller_info <- function (fmtstring = NULL, level = 1) # https://stackoverflow.com/q/59537482/684229
{
    x <- .traceback(x = level + 1)

    i <- 1
    repeat { # loop for subexpressions case; find the first one with source reference
        srcref <- getSrcref(x[[i]])
        if (is.null(srcref)) {
            if (i < length(x)) {
                i <- i + 1
                next;
            } else {
                warning("caller_info(): not found\n")
                return (NULL)
            }
        }
        srcloc <- list(fun = getSrcref(x[[i+1]]), file = getSrcFilename(x[[i]]), line = getSrcLocation(x[[i]]))
        break;
    }

    if (is.null(fmtstring))
        return (srcloc)

    fmtstring <- sub("%f", paste0(srcloc$fun, collapse = ""), fmtstring)
    fmtstring <- sub("%s", srcloc$file, fmtstring)
    fmtstring <- sub("%l", srcloc$line, fmtstring)
    fmtstring
}

Así es como se usa:

f <- function ()
{
    str <- caller_info("Called from %f at %s#%l\n")
    cat(str)
}

La única limitación (menor) es que cuando se llama en subexpresiones como cat(caller_info("Called from %f at %s#%l\n"))o cat(paste0(caller_info("Called from %f at %s#%l\n"))), R confunde estas cosas de subexpresión como niveles de pila, lo que lo confunde. Así que mejor evite el uso de este contenedor en expresiones.

TMS
fuente
¡Agradable! No sabía sobre el argumento opcional para .traceback().
usuario2554330
1
El único cambio que haría es utilizar las funciones del extractor y getSrcref, en lugar de trabajar directamente en los atributos. El formato interno puede cambiar. getSrcFilenamegetSrcLocation
user2554330
@ user2554330 gracias, gran comentario! :-) He actualizado mi contenedor.
TMS
0

No hay funciones fáciles para darle lo que está pidiendo, pero para fines de depuración puede llamar browser()a una función y luego ejecutar el wherecomando para ver la pila de llamadas actual. Por ejemplo, puede ver algo como esto:

where 1: calls()
where 2 at ~/temp/test.R#6: print(calls())
where 3 at ~/temp/test.R#9: f()
where 4: eval(ei, envir)
where 5: eval(ei, envir)
where 6: withVisible(eval(ei, envir))
where 7: source("~/temp/test.R", echo = TRUE)

Esto proporciona ubicaciones para un par de llamadas, pero no todas.

Si realmente quieres algo que se imprima a medida que avanzas (como las macros __LINE__y __FILE__en C / C ++), es un poco más difícil. Esto imprime la ubicación actual:

cat("This is line ", getSrcLocation(function() {}, "line"),
  " of ", getSrcFilename(function() {}))

No todas las funciones tienen nombres, y las funciones R no saben con qué nombre las llamó, pero puede ver la llamada actual usando sys.call(). Entonces esto imprime todo:

  cat("This is line ", getSrcLocation(function() {}, "line"),
      " of ", getSrcFilename(function() {}), 
      " called as", deparse(sys.call()), 
      "\n")

que podría imprimir

This is line  3  of  test.R  called as f() 

sys.call tiene un argumento para subir la pila, pero no conozco una forma de obtener la información del número de línea.

Puede obtener la ubicación del inicio de la función que realizó la llamada actual utilizando

cat("Called from ", getSrcFilename(sys.function(-1)), " line ", getSrcLocation(sys.function(-1), "line"), 
    " as ", deparse(sys.call()), "\n")

que le mostrará el código que realizó la llamada, pero el número de línea es solo para la función de la que proviene. ¡Es un buen argumento para mantener sus funciones cortas!

usuario2554330
fuente
hmm ... esto es interesante ... pero es realmente importante para mí obtener el número de línea y la función de la llamada de esta función (desde donde se llama). El número de línea actual es claro y no interesante.
TMS
Luego use browser(), posiblemente activado por un error o advertencia, o use cat("Called from ",getSrcFilename(sys.function(-1)), " line ",getSrcLocation(sys.function(-1), "line"), "\n"), que le dará la ubicación de la función, no la ubicación real de la llamada.
user2554330