Listing functions with debug flag set in R

2020-06-09 05:03发布

问题:

I am trying to find a global counterpart to isdebugged() in R. My scenario is that I have functions that make calls to other functions, all of which I've written, and I am turning debug() on and off for different functions during my debugging. However, I may lose track of which functions are set to be debugged. When I forget and start a loop, I may get a lot more output (nuisance, but not terrible) or I may get no output when some is desired (bad).

My current approach is to use a function similar to the one below, and I can call it with listDebugged(ls()) or list the items in a loaded library (examples below). This could suffice, but it requires that I call it with the list of every function in the workspace or in the packages that are loaded. I can wrap another function that obtains these. It seems like there should be an easier way to just directly "ask" the debug function or to query some obscure part of the environment where it is stashing the list of functions with the debug flag set.

So, a two part question:

  1. Is there a simpler call that exists to query the functions with the debug flag set?
  2. If not, then is there any trickery that I've overlooked? For instance, if a function in one package masks another, I suspect I may return a misleading result.

I realize that there is another method I could try and that is to wrap debug and undebug within functions that also maintain a hidden list of debugged function names. I'm not yet convinced that's a safe thing to do.

UPDATE (8/5/11): I searched SO, and didn't find earlier questions. However, SO's "related questions" list has shown that an earlier question that is similar, though the function in the answer for that question is both more verbose and slower than the function offered by @cbeleites. The older question also doesn't provide any code, while I did. :)

The code:

listDebugged    <- function(items){
    isFunction  <- vector(length = length(items))
    isDebugged  <- vector(length = length(items))

    for(ix in seq_along(items)){
        isFunction[ix]  <- is.function(eval(parse(text = items[ix])))
    }

    for(ix in which(isFunction == 1)){
        isDebugged[ix]  <- isdebugged(eval(parse(text = items[ix])))
    }
    names(isDebugged)   <- items
    return(isDebugged)
}

# Example usage
listDebugged(ls())
library(MASS)
debug(write.matrix)
listDebugged(ls("package:MASS"))

回答1:

Here's my throw at the listDebugged function:

ls.deb  <- function(items = search ()){
  .ls.deb <-  function (i){
    f <- ls (i)
    f <- mget (f, as.environment (i), mode = "function",

               ## return a function that is not debugged
               ifnotfound = list (function (x) function () NULL)
               )

    if (length (f) == 0)
      return (NULL)

    f <- f [sapply (f, isdebugged)]
    f <- names (f)

    ## now check whether the debugged function is masked by a not debugged one
    masked <- !sapply (f, function (f) isdebugged (get (f)))

    ## generate pretty output format:
    ## "package::function"  and "(package::function)" for masked debugged functions
    if (length (f) > 0) {
      if (grepl ('^package:', i)) {
        i <- gsub ('^package:', '', i)
        f <- paste (i, f, sep = "::")
      }

      f [masked] <- paste ("(", f [masked], ")", sep = "")

      f
    } else {
      NULL
    }
  }


  functions <- lapply (items, .ls.deb)
  unlist (functions)
}
  • I chose a different name, as the output format are only the debugged functions (otherwise I easily get thousands of functions)
  • the output has the form package::function (or rather namespace::function but packages will have namespaces pretty soon anyways).
  • if the debugged function is masked, output is "(package::function)"
  • the default is looking throught the whole search path


回答2:

This is a simple one-liner using lsf.str:

which(sapply(lsf.str(), isdebugged))

You can change environments within the function, see ?lsf.str for more arguments.



回答3:

Since the original question, I've been looking more and more at Mark Bravington's debug package. If using that package, then check.for.traces() is the appropriate command to list those functions that are being debugged via mtrace.

The debug package is worth a look if one is spending much time with the R debugger and various trace options.



回答4:

@cbeleites I like your answer, but it didn't work for me. I got this to work but it is less functional than yours above (no recursive checks, no pretty print)

require(plyr)
debug.ls <- function(items = search()){
  .debug.ls <- function(package){
    f <- ls(package)
    active <- f[which(aaply(f, 1, function(x){
        tryCatch(isdebugged(x), error = function(e){FALSE}, finally=FALSE)
      }))]
    if(length(active)==0){
      return(NULL)
    }
    active
  }

  functions <- lapply (items, .debug.ls)
  unlist (functions)
}


回答5:

I constantly get caught in the browser window frame because of failing to undebug functions. So I have created two functions and added them to my .Rprofile. The helper functions are pretty straight forward.

require(logging)

# Returns a vector of functions on which the debug flag is set
debuggedFuns <- function() {
  envs <- search()
  debug_vars <- sapply(envs, function(each_env) {
    funs <- names(Filter(is.function, sapply(ls(each_env), get, each_env)))
    debug_funs <- Filter(isdebugged, funs)
    debug_funs
  })
  return(as.vector(unlist(debug_vars)))
}

# Removes the debug flag from all the functions returned by `debuggedFuns`
unDebugAll <- function(verbose = TRUE) {
  toUnDebug <- debuggedFuns()
  if (length(toUnDebug) == 0) {
    if (verbose) loginfo('no Functions to `undebug`')
    return(invisible())
  } else {
    if (verbose) loginfo('undebugging [%s]', paste0(toUnDebug, collapse = ', '))
    for (each_fn in toUnDebug) {
      undebug(each_fn)
    }
    return(invisible())
  }
}

I have tested them out, and it works pretty well. Hope this helps!