Wie erhalte ich die Zeilennummer eines Funktionsaufrufs in R?

8

Für Debug-Zwecke möchte ich eine Zeilennummer (und einen Funktionsnamen) des Ortes drucken, von dem aus die aktuelle Funktion aufgerufen wurde. Wie bekomme ich das in R?

Ich habe eine Lösung zum Abrufen des Quelldateinamens gesehen. Aber wie erhalte ich die Zeilennummer und den Funktionsnamen?]

BEARBEITEN: Ich habe herausgefunden, wie ich diese Daten traceback()in irgendeiner Form abrufen kann. Traceback kann sie ausdrucken, bin mir aber nicht sicher, wie ich die Informationen daraus entschlüsseln soll:

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
quelle

Antworten:

4

Eine Lösung gefunden! Habe es aus dem Code von 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

Schrieb eine schöne Wrapper-Funktion dafür:

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

So wird es verwendet:

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

Die einzige (geringfügige) Einschränkung besteht darin, dass R , wenn es in Unterausdrücken wie cat(caller_info("Called from %f at %s#%l\n"))oder aufgerufen wird, cat(paste0(caller_info("Called from %f at %s#%l\n")))diese Unterausdrucksgegenstände verwirrend als Stapelebenen zählt, was es durcheinander bringt. Vermeiden Sie daher besser die Verwendung dieses Wrappers in Ausdrücken.

TMS
quelle
Nett! Ich wusste nichts über das optionale Argument zu .traceback().
user2554330
1
Die einzige Änderung , die ich machen würde , ist die Extraktor - Funktionen zu verwenden getSrcref, getSrcFilenameund getSrcLocationstatt direkt auf den Attributen arbeiten. Das interne Format kann sich ändern.
user2554330
@ user2554330 danke, toller Kommentar! :-) Ich habe meinen Wrapper aktualisiert.
TMS
0

Es gibt keine einfachen Funktionen, mit denen Sie das bekommen, wonach Sie fragen, aber zu Debugging-Zwecken können Sie browser()eine Funktion aufrufen und dann den whereBefehl ausführen , um den aktuellen Aufrufstapel anzuzeigen. Zum Beispiel könnten Sie so etwas sehen:

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)

Dies gibt Standorte für einige der Anrufe an, jedoch nicht für alle.

Wenn Sie wirklich etwas wollen, das unterwegs gedruckt wird (wie die __LINE__und __FILE__Makros in C / C ++), ist es etwas schwieriger. Dies druckt den aktuellen Speicherort:

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

Nicht alle Funktionen haben Namen, und R-Funktionen wissen nicht, unter welchem ​​Namen Sie sie aufgerufen haben, aber Sie können den aktuellen Aufruf mit sehen sys.call(). Das druckt also alles:

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

was drucken könnte

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

sys.call hat ein Argument, um den Stapel nach oben zu verschieben, aber ich kenne keine Möglichkeit, die Zeilennummerninformationen abzurufen.

Sie können den Ort des Starts der Funktion abrufen, mit der der aktuelle Aufruf ausgeführt wurde

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

Hier wird der Code angezeigt, der den Anruf getätigt hat, aber die Zeilennummer gilt nur für die Funktion, von der er stammt. Es ist ein gutes Argument, um Ihre Funktionen kurz zu halten!

user2554330
quelle
hmm ... das ist interessant ... aber es ist wirklich wichtig für mich, die Zeilennummer und die Funktion des Aufrufs dieser Funktion zu erhalten (von wo aus sie aufgerufen wird). Die aktuelle Zeilennummer ist klar und nicht interessant.
TMS
Verwenden Sie dann browser(), möglicherweise ausgelöst durch einen Fehler oder eine Warnung, oder verwenden Sie cat("Called from ",getSrcFilename(sys.function(-1)), " line ",getSrcLocation(sys.function(-1), "line"), "\n"), um den Funktionsort und nicht den tatsächlichen Anrufort anzugeben.
user2554330