#r
Вопрос:
Я пытаюсь настроить способ в R для печати сведений о каждом задании во время выполнения кода R. Так, например, если код x <- 1
будет запущен, то x has been assigned 1
он будет автоматически напечатан.
Возможно ли это? У меня есть две мысли о том, как это можно сделать, но я не могу понять, возможно ли то или другое.
- переопределите
=
примитив, чтобы он также печатал сообщение - есть назначение, запускающее другую функцию для запуска
Комментарии:
1. Неужели это в буквальном смысле должно регистрироваться именно так? Или этого
> print( x <- 1 )
достаточно? Вы можете обернуть2. Я настоятельно рекомендую не переопределять
=
для целей ведения журнала. Даже если бы было безопасно переопределить примитив таким образом, поймите, что каждая отдельная функция в R использует его: для каждой отдельной строки журнала, которую вы хотите увидеть, вы, вероятно, увидите сотни внутренних (базовых R) применений, которые разрушат цель (сигнал против шума).3. Я надеялся на решение, которое можно было бы реализовать на более высоком уровне, чтобы не приходилось редактировать каждую строку кода с назначением. В идеале фрагмент кода можно было бы поместить в верхнюю часть более старого файла кода, и этого было бы достаточно для создания всех необходимых отпечатков.
4. @r2evans это очень хороший момент. Надеюсь, я смогу найти другой обходной путь
5. @r2evans Это хорошее решение, но я пытаюсь реализовать то, что не потребует возврата к редактированию старого кода.
Ответ №1:
одним из возможных решений, но требующим редактирования кода, было бы
# custom assignment function -----------------------------------------------------------------
`%<-%` <- function (lhs, rhs) {
cl <- match.call()
lhs <- substitute(lhs)
env <- parent.frame()
message("Info: `", lhs, "` defined as `", enquote(cl$rhs)[2], "`")
invisible(eval(assign(x = paste(lhs),
value = rhs,
envir = env))
)
}
# some tests ----------------------------------------------------------------------------------
ad %<-% c(1,2,33)
#> Info: `ad` defined as `c(1, 2, 33)`
ac %<-% 22
#> Info: `ac` defined as `22`
ad %<-% 22
#> Info: `ad` defined as `22`
df <- mtcars
df %<-% mtcars
#> Info: `df` defined as `mtcars`
Если вы не хотите изменять файлы, вы можете определить измененную source()
функцию, чтобы заменить назначения новой определенной %<-%
функцией.
source_loudly <- function(filePath, ...) {
file_con <- file(filePath, open = "r")
txt <- readLines(file_con)
close(file_con)
txt_mod <- gsub(pattern = "<-", replace = "%<-%", x = txt)
source(textConnection(txt_mod), ...)
}
filePath <- "R/bits/example.R" # point to a local file on your pc
source_loudly(filePath = filePath, echo = T)
Создано 2021-03-19 пакетом reprex (v1.0.0)
Ответ №2:
Вот хак для добытчика/сеттера, который подходит близко, не стоя слишком дорого. Хотя это требует от вас изменения существующего кода, у него есть преимущество в том, что вы можете изменить первоначальное назначение на list
вместо tracer
, и все продолжает работать без изменений.
tracer <- local({ .e <- NULL function(..., name = "unk") { .e <<- list(...) .e$.name <<- name `class<-`(.e, c("tracer", "environment")) } }) `[.tracer` <- `[[.tracer` <- `$.tracer` <- function(x, i) { cat(sprintf("get: %sn", deparse(substitute(i)))) NextMethod() } `[<-.tracer` <- `[[<-.tracer` <- `
lt;-.tracer` <- function(x, i, value) {
cat(sprintf("set: %s <- %sn", deparse(substitute(i)),
substr(paste(deparse(substitute(value)), collapse = " "), 1, 80)))
NextMethod()
}
Примечания:
deparse
имеет тенденцию разбивать длинные строки на вектор строк; здесь это смягчается с помощьюpaste(..., collapse=" ")
;- … но длинные буквальные значения (например, кадры) могут немного раздражать в журналах, поэтому я произвольно выбрал
substr(., 1, 80)
разумный размер для регистрации. - это намекает на одну проблему, о которой я расскажу ниже: это не говорит вам, какие столбцы были изменены, просто объект был обновлен.
Демонстрация с «простыми» объектами:
quux <- tracer(a=1, b=2:3, d=list(pi, "a"), mt=mtcars[1:2,])
quux$a
# get: "a"
# [1] 1
quux$a <- 11
# set: "a" <- 11
quux$b
# get: "b"
# [1] 2 3
quux$b <- 2:5
# set: "b" <- 2:5
quux$b
# get: "b"
# [1] 2 3 4 5
Пока все идет хорошо. Теперь в списке:
quux$d
# get: "d"
# [[1]]
# [1] 3.141593
# [[2]]
# [1] "a"
quux$d[[1]]
# get: "d"
# [1] 3.141593
quux$d[[1]] <- pi^2
# get: "d"
# set: "d" <- list(9.86960440108936, "a")
Последнее нуждается в некотором объяснении, особенно в отношении порядка операций. Задание действительно `[[<-`(quux$d, 1, pi^2)
есть , которое не отслеживается. Это корректирует первый элемент списка, а затем назначает этот новый список обратно quux$d
, где наш $<-.tracer
видит это переназначение полного списка.
Это не совсем неразумно для небольших объектов, но с более крупными объектами это становится немного более раздражающим:
quux$mt$cyl
# get: "mt"
# [1] 6 6
quux$mt$cyl <- quux$mt$cyl 5
# get: "mt"
# get: "mt"
# set: "mt" <- structure(list(mpg = c(21, 21), cyl = c(11, 11), disp = c(160, 160), hp = c(110, 110), drat = c(3.9, 3.9), wt = c(2.62, 2.875 ), qsec = c(16.46, 17.02), vs = c(0, 0), am = c(1, 1), gear = c(4, 4), c
quux$mt$cyl
# get: "mt"
# [1] 11 11
Аналогично, для задания мы видим как первый шаг «получить», так и последующее переназначение всего объекта. (Это отсечение, потому что я использовал substr(., 1, 80)
.)
Кроме того, обратите внимание , что в обоих quux$d
и quux$mt
функции трассировки никогда не видят корректируемый подэлемент или столбец. Поскольку R упорядочивает операции так, как он это делает, наши функции отслеживания не могут (легко) выявить, что там происходит.
Теперь, когда вы будете готовы удалить этот уровень ведения журнала активности, просто замените свой первоначальный вызов на tracer(.)
«с list(.)
«, и все операции продолжат работать, но без ведения журнала.
quux <- list(a=1, b=2:3, d=list(pi, "a"), mt=mtcars[1:2,])
quux$a
# [1] 1
quux$a <- 11
quux$b
# [1] 2 3
quux$b <- 2:5
quux$b
# [1] 2 3 4 5
quux$d
# [[1]]
# [1] 3.141593
# [[2]]
# [1] "a"
quux$d[[1]]
# [1] 3.141593
quux$d[[1]] <- pi^2
quux$mt$cyl
# [1] 6 6
quux$mt$cyl <- quux$mt$cyl 5
quux$mt$cyl
# [1] 11 11