I'm creating a custom S3 object in R, but when I print it the attributes also gets printed. For example:
x <- 1:3
x <- structure(x, class = "myclass")
print(x)
#> [1] 1 2 3
#> attr(,"class")
#> [1] "myclass"
Fair enough, I haven't yet added a print
method. Many answers here in SO and other places recommend removing the attributes from the object, and then printing it, like so (also returning the object invisibly to maintain the print
original property):
x <- 1:3
x <- structure(x, class = "myclass")
print.myclass <- function(x, ...) {
print(unclass(x), ...)
return(invisible(x))
}
print(x)
#> [1] 1 2 3
This approach, however, creates a copy of x
, and I don't want that. We can see that with tracemem()
:
x <- 1:3
x <- structure(x, class = "myclass")
tracemem(x)
#> [1] "<0x558e1a9adcc8>"
print.myclass <- function(x, ...) {
print(unclass(x), ...)
return(invisible(x))
}
print(x)
#> tracemem[0x558e1a9adcc8 -> 0x558e1aa63918]: print print.myclass print
#> [1] 1 2 3
The original print
doesn't create a copy of x
:
x <- 1:3
x <- structure(x, class = "myclass")
tracemem(x)
#> [1] "<0x557799ebeb88>"
print(x)
#> [1] 1 2 3
#> attr(,"class")
#> [1] "myclass"
Hadley suggests, in his Advanced R book, that one could use NextMethod()
to prevent creating a copy in a subset method. I have spent some time thinking on how to prevent creating a copy when printing an S3 object with an attribute, but I couldn't figure out how to do so.
In fact, I just tested printing a tibble
and a copy is also created:
z <- tibble::tibble(x = 1, y = 2)
tracemem(z)
#> [1] "<0x55e2aa368c48>"
print(z)
#> tracemem[0x55e2aa368c48 -> 0x55e2aa339e48]: lapply tbl_subset_row [.tbl_df [ do.call head.data.frame head as.data.frame tbl_format_setup.tbl tbl_format_setup_ tbl_format_setup format.tbl format writeLines print.tbl
#> # A tibble: 1 x 2
#> x y
#> <dbl> <dbl>
#> 1 1 2
But a data.table
doesn't:
z <- data.table::data.table(x = 1, y = 2)
tracemem(z)
#> [1] "<0x55d64b2bd310>"
print(z)
#> x y
#> 1: 1 2
So I guess that this problem is solvable, but I haven't figured out how...
EDIT:
Ok, I did some reading on print.data.frame
and it solves this problem by converting the data.frame
to a matrix
and then printing this matrix. This is a very clever solution, and I guess data.table
uses something similar.
More generally speaking, however, this wouldn't work for some other type of objects (e.g. those that can't be formatted as a matrix, for example). Take this one:
z <- list(a = data.frame(a = 1, b = 2), b = data.frame(c = 3, d = 4))
z <- structure(z, class = "myotherclass")
tracemem(z)
#> [1] "<0x56298a95fd48>"
print(z)
#> $a
#> a b
#> 1 1 2
#>
#> $b
#> c d
#> 1 3 4
#>
#> attr(,"class")
#> [1] "myotherclass"
print.myotherclass <- function(x, ...) {
print(unclass(x), ...)
return(invisible(x))
}
print(z)
#> tracemem[0x56298a95fd48 -> 0x56298aaa58a8]: print print.myotherclass print
#> $a
#> a b
#> 1 1 2
#>
#> $b
#> c d
#> 1 3 4
How'd you do it in this case?