In R, how can I print the name of an object reliably from a custom print function?

Viewed 462

I created a class in R, and am trying to create a print function for that class. I'd like the print function to print the name of the object being passed to the print function. Using the standard deparse(substitute()) I can get the name of the variable, and this works when I call the print() function directly. But it doesn't work when I just run the variable from R Studio. It is obviously calling my print function. But there is some indirection that is causing it to lose the variable name. Here is an example:

# Define class 'myobj'
myobj <- function(val) {
  
  obj <- structure(list(), class = c("myobj", "list")) 
  
  obj$value = val
  
  return(obj)
}

# Write custom print function for 'myobj' class
#' @export
print.myobj <- function(x, ...) {
  
  nm <- deparse1(substitute(x, env = environment()))
  cat(paste0("My object name: ", nm, "\n"))
  cat(paste0("My object value: ", x$value, "\n"))
      
  invisible(x)
}

myInstance <- myobj(123)

# Prints name and value.  
print(myInstance)

#> My object name: myInstance
#> My object value: 123

# Prints value, but name is local variable 'x'. 
myInstance

#> My object name: x
#> My object value: 123

It seems like when you run the variable directly, it is getting nested in another frame or environment, such that the deparse(substitute()) isn't working.

How can I get my print function to print the name of the variable reliably, no matter how it is called?

1 Answers

The autoprinting documentation says it is handled by print.c.

This function looks like this.

static void PrintObjectS3(SEXP s, R_PrintData *data)
{
    /*
      Bind value to a variable in a local environment, similar to
      a local({ x <- <value>; print(x) }) call. This avoids
      problems in previous approaches with value duplication and
      evaluating the value, which might be a call object.
    */
    SEXP xsym = install("x");
    SEXP mask = PROTECT(NewEnvironment(R_NilValue, R_NilValue, data->env));
    defineVar(xsym, s, mask);

    /* Forward user-supplied arguments to print() */
    SEXP fun = PROTECT(findFun(install("print"), R_BaseNamespace));
    SEXP args = PROTECT(cons(xsym, data->callArgs));
    SEXP call = PROTECT(lcons(fun, args));

    eval(call, mask);

    defineVar(xsym, R_NilValue, mask); /* To eliminate reference to s */
    UNPROTECT(4); /* mask, fun, args, call */
}

Note that it says

similar to a local({ x <- <value>; print(x) })

So autoprinting does not directly call print(myInstance). It does things with the symbol x so I think getting the variable name in the way you want is not possible.

The difference can be seen by looking at the traceback.

print.myobj <- function(x, ...) {
  stop()
}

print(myInstance)
#> (Traceback)
#> 3. stop() 
#> 2. print.myobj(myInstance) 
#> 1. print(myInstance) 

myInstance
#> (Traceback)
#> 3. stop() 
#> 2. print.myobj(x) 
#> 1. (function (x, ...) 
#>    UseMethod("print"))(x)
Related