Andy Baxter
Andy Baxter

Reputation: 7626

Getting the name of object passed to `print` when calling object directly (not expressing the `print` function)

I'm trying to define the print method for my new object and use the name of the object passed to print using deparse(substitute(y)). This works perfectly using the print function explicitly:

obj <- structure(list(x = 1), 
                 class = "new_obj")

print.new_obj <- function(y){
  cat("New object name:\n")
  print(deparse(substitute(y)))
}

print(obj)
# New object name:
#   [1] "obj"

But when the object is called by name on its own the resulting print function doesn't detect the name:

obj
# New object name:
#   [1] "x"

Is there a standard way to change the behaviour of the implicit call to print when passing an object name on its own?

EDIT: have changed the function argument to y to represent object being passed, to demonstrate that "x" is returned no matter what in the second call.

Upvotes: 7

Views: 242

Answers (1)

Allan Cameron
Allan Cameron

Reputation: 173793

It is easier to explain what's going on than it is to fix it. If we start by looking at the generic print we can see it simply dispatches the class-appropriate print method via UseMethod("print"):

print
#> function (x, ...) 
#> UseMethod("print")

So when you call print(obj), you are calling the generic function print(obj) first, which then calls print.new_obj(obj). We can confirm this by adding print(sys.calls()) to your print method:

print.new_obj <- function(y){
  print(sys.calls())
  cat("New object name:\n")
  cat(deparse(substitute(y)))
}

print(obj)
#> [[1]]
#> print(obj)
#> 
#> [[2]]
#> print.new_obj(obj)
#> 
#> New object name:
#> obj

So far, so good, and I suspect you already knew all this.

What happens now, when you just type obj into the console?

obj
#> [[1]]
#> (function (x, ...) 
#> UseMethod("print"))(x)
#> 
#> [[2]]
#> print.new_obj(x)
#> 
#> New object name:
#> x

Now we can see where the x comes from. It is taken from a behind-the-scenes call to the generic print which is actually called as an unnamed function. Hence the name of the variable is not actually included in the call stack. There are other questions on SO where it says this makes the problem insoluble. This isn't true; it just means you will need to look outside of the call stack for your object:

print.new_obj <- function(y){
  obj_name <- deparse(substitute(x, parent.frame()))
  if (obj_name != "x")
  {
    obj_name <- names(which(sapply(ls(envir = parent.frame(2)), function(v) 
      identical(y, get(v, envir = parent.frame(2))))))[1]
    cat("New object name:\n", obj_name)
  }
  else cat("New object name:\n", deparse(substitute(y)))
}

print(obj)
#> New object name:
#>  obj
obj
#> New object name:
#>  obj

Of course, you wouldn't want to use this in production code, for all sorts of reasons. It is not particularly useful or logical for a data structure to know what name it has been assigned in a particular environment, and would not be an idiomatic way to write a package for other users.

Still, nice to know it is possible.

Upvotes: 3

Related Questions