Что такое быстрый способ установки кода отладки в заданной строке в функции?
Преамбула:
R trace()
- мощный инструмент отладки, позволяющий пользователям "вставлять код отладки в выбранные места в любую функцию". К сожалению, использование его из командной строки может быть довольно трудоемким.
Как искусственный пример, скажем, я хочу вставить код отладки, который будет сообщать интервал между тиками, рассчитанный pretty.default()
. Я хотел бы вставить код сразу после вычисления значения delta
, примерно в четырех строках от нижней части определения функции. (Введите pretty.default
, чтобы увидеть, где я имею в виду.)
Чтобы указать эту строку, мне нужно найти, какой шаг в коде, которому он соответствует. Ответ оказывается шагом list(c(12, 3, 3))
, который я включаю в нуль, выполнив следующие шаги:
as.list(body(pretty.default))
as.list(as.list(body(pretty.default))[[12]])
as.list(as.list(as.list(body(pretty.default))[[12]])[[3]])
as.list(as.list(as.list(body(pretty.default))[[12]])[[3]])[[3]]
Затем я могу вставить код отладки следующим образом:
trace(what = 'pretty.default',
tracer = quote(cat("\nThe value of delta is: ", delta, "\n\n")),
at = list(c(12,3,3)))
## Try it
a <- pretty(c(1, 7843))
b <- pretty(c(2, 23))
## Clean up
untrace('pretty.default')
Вопросы:
Итак, вот мои вопросы: есть ли способ распечатать функцию (или проанализированную ее версию) с линиями, красиво помеченными шагами, к которым они принадлежат? (Согласно Venables и Ripley, S-plus имеет функцию tprint()
, которая "производит нумерованный список тела функции для использования с аргументом at
trace
", но R, похоже, не имеет эквивалента. ) Альтернативно, есть ли еще один простой способ из командной строки быстро установить код отладки для определенной строки внутри функции?
Добавление:
Я использовал пример pretty.default()
, потому что он достаточно ручный, но с реальными/интересными функциями, многократно используя as.list()
быстро становится утомительным и отвлекающим. Вот пример:
as.list(as.list(as.list(as.list(as.list(as.list(as.list(as.list(as.list(body(#
model.frame.default))[[26]])[[3]])[[2]])[[4]])[[3]])[[4]])[[4]])[[4]])[[3]]
Ответы
Ответ 1
Вот удобная оболочка для обнаружения куска:
library(codetools)
ff <- function(f, tar) {
cc <- function(e, w) {
if(length(w$pos) > 0 &&
grepl(w$tar, paste(deparse(e), collapse = "\n"), fixed = TRUE)) {
cat(rev(w$pos), ": ", deparse(e), "\n")
w$ret$vals <- c(w$ret$vals, list(rev(w$pos)))
}
w$pos <- c(0, w$pos)
for (ee in as.list(e)){
if (!missing(ee)) {
w$pos[1] <- w$pos[1] + 1
walkCode(ee, w)
}
}
}
w <- list(pos = c(),
tar = tar,
ret = new.env(),
handler = function(v, w) NULL,
call = cc,
leaf = function(e, w) NULL)
walkCode(body(f), w = w)
w$ret$vals
}
а затем
> r <- ff(pretty.default, "delta <- diff(range(z$l, z$u))/z$n")
12 : if (!eps.correct && z$n) { delta <- diff(range(z$l, z$u))/z$n if (any(small <- abs(s) < 1e-14 * delta)) s[small] <- 0 }
12 3 : { delta <- diff(range(z$l, z$u))/z$n if (any(small <- abs(s) < 1e-14 * delta)) s[small] <- 0 }
12 3 2 : delta <- diff(range(z$l, z$u))/z$n
> r
[[1]]
[1] 12
[[2]]
[1] 12 3
[[3]]
[1] 12 3 2
> r <- ff(model.frame.default, "stop(gettextf(\"factor '%s' has new level(s) %s\", nm, paste(nxl[m],")
26 3 2 4 3 4 4 4 3 : stop(gettextf("factor '%s' has new level(s) %s", nm, paste(nxl[m], collapse = ", ")), domain = NA)
> r
[[1]]
[1] 26 3 2 4 3 4 4 4 3
и вы можете определить трассировщик по содержанию:
traceby <- function(fun, tar, cer) {
untrace(deparse(substitute(fun)))
r <- ff(fun, tar)
r <- r[which.max(sapply(r, length))]
trace(what = deparse(substitute(fun)), tracer = cer, at = r)
}
то
> traceby(pretty.default, "if (any(small <- abs(s) < 1e-14 * delta)) s[small] <- 0", quote(cat("\nThe value of delta is: ", delta, "\n\n")))
Untracing function "pretty.default" in package "base"
12 3 3 : if (any(small <- abs(s) < 1e-14 * delta)) s[small] <- 0
Tracing function "pretty.default" in package "base"
[1] "pretty.default"
> a <- pretty(c(1, 7843))
Tracing pretty.default(c(1, 7843)) step 12,3,3
The value of delta is: 2000
> b <- pretty(c(2, 23))
Tracing pretty.default(c(2, 23)) step 12,3,3
The value of delta is: 5
Ответ 2
Здесь что-то очень хорошо работает для pretty.default
и model.frame.default
.
print.func <- function(func, ...) {
str(as.list.func(func, ...), comp.str="")
}
as.list.func <- function(func, recurse.keywords = c("{", "if", "repeat", "while", "for", "switch")) {
as.list.func.recurse(body(func), recurse.keywords)
}
as.list.func.recurse <- function(x, recurse.keywords) {
x.list <- as.list(x)
top <- deparse(x.list[[1]])
if (length(x.list) > 1 && top %in% recurse.keywords) {
res <- lapply(x.list, as.list.func.recurse, recurse.keywords)
setNames(res, seq_along(res))
} else {
x
}
}
Результаты для pretty.default
:
> print.func(pretty.default)
List of 13
1 : symbol {
2 : language x <- x[is.finite(x <- as.numeric(x))]
3 :List of 3
..$ 1: symbol if
..$ 2: language length(x) == 0L
..$ 3: language return(x)
4 :List of 3
..$ 1: symbol if
..$ 2: language is.na(n <- as.integer(n[1L])) || n < 0L
..$ 3: language stop("invalid 'n' value")
5 :List of 3
..$ 1: symbol if
..$ 2: language !is.numeric(shrink.sml) || shrink.sml <= 0
..$ 3: language stop("'shrink.sml' must be numeric > 0")
6 :List of 3
..$ 1: symbol if
..$ 2: language (min.n <- as.integer(min.n)) < 0 || min.n > n
..$ 3: language stop("'min.n' must be non-negative integer <= n")
7 :List of 3
..$ 1: symbol if
..$ 2: language !is.numeric(high.u.bias) || high.u.bias < 0
..$ 3: language stop("'high.u.bias' must be non-negative numeric")
8 :List of 3
..$ 1: symbol if
..$ 2: language !is.numeric(u5.bias) || u5.bias < 0
..$ 3: language stop("'u5.bias' must be non-negative numeric")
9 :List of 3
..$ 1: symbol if
..$ 2: language (eps.correct <- as.integer(eps.correct)) < 0L || eps.correct > 2L
..$ 3: language stop("'eps.correct' must be 0, 1, or 2")
10: language z <- .C("R_pretty", l = as.double(min(x)), u = as.double(max(x)), n = n, min.n, shrink = as.double(shrink.sml), high.u.fact = as.double(c(high.u.bias, ...
11: language s <- seq.int(z$l, z$u, length.out = z$n + 1)
12:List of 3
..$ 1: symbol if
..$ 2: language !eps.correct && z$n
..$ 3:List of 3
.. ..$ 1: symbol {
.. ..$ 2: language delta <- diff(range(z$l, z$u))/z$n
.. ..$ 3:List of 3
.. .. ..$ 1: symbol if
.. .. ..$ 2: language any(small <- abs(s) < 1e-14 * delta)
.. .. ..$ 3: language s[small] <- 0
13: symbol s
Результаты для model.frame.default
:
> print.func(model.frame.default)
List of 29
1 : symbol {
2 : language possible_newdata <- !missing(data) && is.data.frame(data) && identical(deparse(substitute(data)), "newdata") && (nr <- nrow(data)) > 0
3 :List of 3
..$ 1: symbol if
..$ 2: language !missing(formula) && nargs() == 1 && is.list(formula) && !is.null(m <- formula$model)
..$ 3: language return(m)
4 :List of 3
..$ 1: symbol if
..$ 2: language !missing(formula) && nargs() == 1 && is.list(formula) && all(c("terms", "call") %in% names(formula))
..$ 3:List of 8
.. ..$ 1: symbol {
.. ..$ 2: language fcall <- formula$call
.. ..$ 3: language m <- match(c("formula", "data", "subset", "weights", "na.action"), names(fcall), 0)
.. ..$ 4: language fcall <- fcall[c(1, m)]
.. ..$ 5: language fcall[[1L]] <- as.name("model.frame")
.. ..$ 6: language env <- environment(formula$terms)
.. ..$ 7:List of 3
.. .. ..$ 1: symbol if
.. .. ..$ 2: language is.null(env)
.. .. ..$ 3: language env <- parent.frame()
.. ..$ 8: language return(eval(fcall, env, parent.frame()))
5 :List of 4
..$ 1: symbol if
..$ 2: language missing(formula)
..$ 3:List of 3
.. ..$ 1: symbol {
.. ..$ 2:List of 3
.. .. ..$ 1: symbol if
.. .. ..$ 2: language !missing(data) && inherits(data, "data.frame") && length(attr(data, "terms"))
.. .. ..$ 3: language return(data)
.. ..$ 3: language formula <- as.formula(data)
..$ 4:List of 3
.. ..$ 1: symbol if
.. ..$ 2: language missing(data) && inherits(formula, "data.frame")
.. ..$ 3:List of 4
.. .. ..$ 1: symbol {
.. .. ..$ 2:List of 3
.. .. .. ..$ 1: symbol if
.. .. .. ..$ 2: language length(attr(formula, "terms"))
.. .. .. ..$ 3: language return(formula)
.. .. ..$ 3: language data <- formula
.. .. ..$ 4: language formula <- as.formula(data)
6 : language formula <- as.formula(formula)
7 :List of 3
..$ 1: symbol if
..$ 2: language missing(na.action)
..$ 3:List of 2
.. ..$ 1: symbol {
.. ..$ 2:List of 4
.. .. ..$ 1: symbol if
.. .. ..$ 2: language !is.null(naa <- attr(data, "na.action")) & mode(naa) != "numeric"
.. .. ..$ 3: language na.action <- naa
.. .. ..$ 4:List of 3
.. .. .. ..$ 1: symbol if
.. .. .. ..$ 2: language !is.null(naa <- getOption("na.action"))
.. .. .. ..$ 3: language na.action <- naa
8 :List of 4
..$ 1: symbol if
..$ 2: language missing(data)
..$ 3: language data <- environment(formula)
..$ 4:List of 4
.. ..$ 1: symbol if
.. ..$ 2: language !is.data.frame(data) && !is.environment(data) && !is.null(attr(data, "class"))
.. ..$ 3: language data <- as.data.frame(data)
.. ..$ 4:List of 3
.. .. ..$ 1: symbol if
.. .. ..$ 2: language is.array(data)
.. .. ..$ 3: language stop("'data' must be a data.frame, not a matrix or an array")
9 :List of 3
..$ 1: symbol if
..$ 2: language !inherits(formula, "terms")
..$ 3: language formula <- terms(formula, data = data)
10: language env <- environment(formula)
11: language rownames <- .row_names_info(data, 0L)
12: language vars <- attr(formula, "variables")
13: language predvars <- attr(formula, "predvars")
14:List of 3
..$ 1: symbol if
..$ 2: language is.null(predvars)
..$ 3: language predvars <- vars
15: language varnames <- sapply(vars, function(x) paste(deparse(x, width.cutoff = 500), collapse = " "))[-1L]
16: language variables <- eval(predvars, data, env)
17: language resp <- attr(formula, "response")
18:List of 3
..$ 1: symbol if
..$ 2: language is.null(rownames) && resp > 0L
..$ 3:List of 3
.. ..$ 1: symbol {
.. ..$ 2: language lhs <- variables[[resp]]
.. ..$ 3: language rownames <- if (is.matrix(lhs)) rownames(lhs) else names(lhs)
19:List of 3
..$ 1: symbol if
..$ 2: language possible_newdata && length(variables)
..$ 3:List of 3
.. ..$ 1: symbol {
.. ..$ 2: language nr2 <- max(sapply(variables, NROW))
.. ..$ 3:List of 3
.. .. ..$ 1: symbol if
.. .. ..$ 2: language nr2 != nr
.. .. ..$ 3: language warning(gettextf("'newdata' had %d rows but variable(s) found have %d rows", nr, nr2), call. = FALSE)
20:List of 3
..$ 1: symbol if
..$ 2: language is.null(attr(formula, "predvars"))
..$ 3:List of 3
.. ..$ 1: symbol {
.. ..$ 2:List of 4
.. .. ..$ 1: symbol for
.. .. ..$ 2: symbol i
.. .. ..$ 3: language seq_along(varnames)
.. .. ..$ 4: language predvars[[i + 1]] <- makepredictcall(variables[[i]], vars[[i + 1]])
.. ..$ 3: language attr(formula, "predvars") <- predvars
21: language extras <- substitute(list(...))
22: language extranames <- names(extras[-1L])
23: language extras <- eval(extras, data, env)
24: language subset <- eval(substitute(subset), data, env)
25: language data <- .Internal(model.frame(formula, rownames, variables, varnames, extras, extranames, subset, na.action))
26:List of 4
..$ 1: symbol if
..$ 2: language length(xlev)
..$ 3:List of 2
.. ..$ 1: symbol {
.. ..$ 2:List of 4
.. .. ..$ 1: symbol for
.. .. ..$ 2: symbol nm
.. .. ..$ 3: language names(xlev)
.. .. ..$ 4:List of 3
.. .. .. ..$ 1: symbol if
.. .. .. ..$ 2: language !is.null(xl <- xlev[[nm]])
.. .. .. ..$ 3:List of 4
.. .. .. .. ..$ 1: symbol {
.. .. .. .. ..$ 2: language xi <- data[[nm]]
.. .. .. .. ..$ 3:List of 3
.. .. .. .. .. ..$ 1: symbol if
.. .. .. .. .. ..$ 2: language is.character(xi)
.. .. .. .. .. ..$ 3:List of 3
.. .. .. .. .. .. ..$ 1: symbol {
.. .. .. .. .. .. ..$ 2: language xi <- as.factor(xi)
.. .. .. .. .. .. ..$ 3: language warning(gettextf("character variable '%s' changed to a factor", nm), domain = NA)
.. .. .. .. ..$ 4:List of 4
.. .. .. .. .. ..$ 1: symbol if
.. .. .. .. .. ..$ 2: language !is.factor(xi) || is.null(nxl <- levels(xi))
.. .. .. .. .. ..$ 3: language warning(gettextf("variable '%s' is not a factor", nm), domain = NA)
.. .. .. .. .. ..$ 4:List of 5
.. .. .. .. .. .. ..$ 1: symbol {
.. .. .. .. .. .. ..$ 2: language xi <- xi[, drop = TRUE]
.. .. .. .. .. .. ..$ 3: language nxl <- levels(xi)
.. .. .. .. .. .. ..$ 4:List of 3
.. .. .. .. .. .. .. ..$ 1: symbol if
.. .. .. .. .. .. .. ..$ 2: language any(m <- is.na(match(nxl, xl)))
.. .. .. .. .. .. .. ..$ 3: language stop(gettextf("factor '%s' has new level(s) %s", nm, paste(nxl[m], collapse = ", ")), domain = NA)
.. .. .. .. .. .. ..$ 5: language data[[nm]] <- factor(xi, levels = xl, exclude = NULL)
..$ 4:List of 3
.. ..$ 1: symbol if
.. ..$ 2: symbol drop.unused.levels
.. ..$ 3:List of 2
.. .. ..$ 1: symbol {
.. .. ..$ 2:List of 4
.. .. .. ..$ 1: symbol for
.. .. .. ..$ 2: symbol nm
.. .. .. ..$ 3: language names(data)
.. .. .. ..$ 4:List of 3
.. .. .. .. ..$ 1: symbol {
.. .. .. .. ..$ 2: language x <- data[[nm]]
.. .. .. .. ..$ 3:List of 3
.. .. .. .. .. ..$ 1: symbol if
.. .. .. .. .. ..$ 2: language is.factor(x) && length(unique(x[!is.na(x)])) < length(levels(x))
.. .. .. .. .. ..$ 3: language data[[nm]] <- data[[nm]][, drop = TRUE]
27: language attr(formula, "dataClasses") <- sapply(data, .MFclass)
28: language attr(data, "terms") <- formula
29: symbol data
Ответ 3
Здесь используется подход, который использует тот факт, что findLineNum()
в пакете utils
может использоваться для определения шага, соответствующего указанной строке в данном исходном файле.
getStep <- function(fun, txt) {
## Create a text file into which the function can dumped
## and from which it can then be sourced
tmpfile <- tempfile()
on.exit(unlink(tmpfile))
dump(fun, file = tmpfile)
## Find the line containing the code of interest
lines <- readLines(tmpfile)
matchlines <- grepl(txt, lines, fixed=TRUE)
if(sum(matchlines) > 1) {
stop(paste(dQuote(txt), "matches more than one line in", fun))
}
linenum <- which(matchlines)
## Use findLineNum() to determine the step corresponding to that line
source(tmpfile)
Step <- list(findLineNum(tmpfile, line=linenum)[[1]]$at)
## Clean up and return
rm(list = fun, envir = .GlobalEnv)
return(Step)
}
## Test it
getStep(fun = "pretty.default",
txt = "if (any(small <- abs(s) < 1e-14 * delta))")
# [[1]]
# [1] 6 3 3
Затем это небольшой шаг для включения getStep()
в функцию, которая вставляет отладочную
код в функции fun
в строке соответствия txt
.
## Define the function
traceLine <- function(fun, txt, tracer) {
Step <- getStep(fun = deparse(substitute(fun)), txt = txt)
trace(what = substitute(fun),
tracer = tracer,
at = Step)
}
## Confirm that it works.
traceLine(fun = pretty.default,
txt = "if (any(small <- abs(s) < 1e-14 * delta))",
tracer = quote(cat("\nThe value of delta is: ", delta, "\n\n")))
a <- pretty(c(1, 7843))
b <- pretty(c(2, 23))
untrace(pretty.default)