У меня есть пакет R, который содержит файл R/globals.R
со следующим содержимым (упрощенно):
utils::globalVariables("COUNTS")
Затем у меня есть функция, которая просто использует эту переменную. Например, R/addx.R
содержит функцию, которая добавляет число к COUNTS
.
addx <- function(x) {
COUNTS + x
}
Это все нормально, когда я делаю devtools::check()
на моем пакете, я не жалуюсь на то, что COUNTS
выходит за рамки addx()
.
Однако, скажем, у меня также есть файл tests/testthtat/test-addx.R
со следующим содержимым:
test_that("addition works", expect_gte(fun(1), 1))
Содержание теста здесь не имеет особого значения, потому что при запуске devtools::test()
я получаю ошибку «объект 'COUNTS' not found».
Что мне не хватает? Как я могу правильно написать этот тест (или настроить свой пакет).
utils::globalVariables("COUNTS")
к R/addx.R
до, внутри или после определения функции.utils::globalVariables("COUNTS")
к tests/testthtat/test-addx.R
во всех местах, которые я мог придумать.COUNTS
(например, с помощью COUNTS <- 0
или <<- 0
) во всех местах tests/testthtat/test-addx.R
, которые я мог придумать.Альтернативная ситуация, о которой я могу думать, заключается в том, что вы ожидаете, что COUNTS
будет определено в вызывающей среде. Если это так, то... Я думаю, что ответ "не делай этого".
@r2evans, есть пара функций, которые выполняют присваивания COUNTS
. clearGlobal()
в основном инициализирует его как COUNTS <- vector()
, а updateGlobal()
пересчитывает COUNTS
как функцию других переданных аргументов. Насколько я знаю, нет случаев, когда COUNTS
определяется вне определения функции (это огромный проект, с которым я не знаком на 100%). Я думаю, что случай, который у нас есть, - это случай из вашего второго комментария, который, я согласен, не идеален, но я боюсь, что что-то сломаю, если изменю реализованную динамику.
Я думаю, вы неправильно понимаете, что делает utils::globalVariables("COUNTS")
. Он просто объявляет, что COUNTS
является глобальной переменной, поэтому, когда анализ кода увидит
addx <- function(x) {
COUNTS + x
}
он не будет жаловаться на использование неопределенной переменной. Тем не менее, вы должны фактически создать переменную, например, с помощью явного
COUNTS <- 0
где-то в вашем источнике. Я думаю, что если вы это сделаете, вам даже не понадобится вызов utils::globalVariables("COUNTS")
, потому что анализ кода увидит глобальное определение.
Там, где вам это нужно, это когда вы делаете какую-то нестандартную оценку, так что не очевидно, откуда берется переменная. Затем вы объявляете его глобальным, и анализ кода не будет об этом беспокоиться. Например, вы можете получить предупреждение о
subset(df, Col1 < 0)
потому что кажется, что она использует глобальную переменную с именем Col1
, но, конечно, это нормально, потому что функция subset()
вычисляет нестандартным образом, позволяя вам включать имена столбцов без записи df$Col
.
Спасибо, добавление COUNTS <- vector()
к тому же сценарию, что и globalVariables()
(для удобства), решило проблему. Я могу поклясться, что пробовал это (см. пункт 3 OP), но я просто рад, что это работает. Скрестим пальцы, это не сломает что-то ниже по течению. Ваше здоровье!
Ответ @ user2554330 отлично подходит для многих вещей.
Если я правильно понимаю, у вас есть COUNTS
, который нужно обновлять, поэтому его размещение в среде пакета может быть проблемой.
Один из методов, который вы можете использовать, — это использование локальных сред.
Две альтернативы:
Если на него всегда будут ссылаться в одной функции, проще всего будет изменить функцию с
myfunc <- function(...) {
# do something
COUNTS <- COUNTS + 1
}
к
myfunc <- local({
COUNTS <- NA
function(...) {
# do something
COUNTS <<- COUNTS + 1
}
})
Что это делает, так это создает локальную среду «вокруг» myfunc
, поэтому, когда он ищет COUNTS
, он будет немедленно найден. Обратите внимание, что он переназначается с использованием <<-
вместо <-
, поскольку последний не будет обновлять версию переменной для другой среды.
На самом деле вы можете получить доступ к этому COUNTS
из другой функции в пакете:
otherfunc <- function(...) {
COUNTScopy <- get("COUNTS", envir = environment(myfunc))
COUNTScopy <- COUNTScopy + 1
assign("COUNTS", COUNTScopy, envir = environment(myfunc))
}
(Не стесняйтесь называть его COUNTS
здесь, я использовал другое имя, чтобы подчеркнуть, что это не имеет значения.)
Хотя использование get
и assign
немного неудобно, оно должно требоваться только дважды для каждой функции, которая должна это делать.
Обратите внимание, что пользователь может добраться до этого, если это необходимо, но ему нужно будет использовать аналогичные механизмы. Возможно, это проблема; в моих пакетах, где мне нужна какая-то форма постоянства, подобная этой, я использовал удобные функции получения/установки.
Вы можете поместить среду в свой пакет, а затем использовать ее как именованный список в функциях вашего пакета:
E <- new.env(parent = emptyenv())
myfunc <- function(...) {
# do something
E$COUNTS <- E$COUNTS + 1
}
otherfunc <- function(...) {
E$COUNTS <- E$COUNTS + 1
}
Нам не нужна пара функций get
/assign
, так как E
(ужасное имя, выбранное из-за его краткости) должно быть видно всем функциям в вашем пакете. Если вам не нужен доступ пользователя, оставьте его неэкспортированным. Если вы хотите, чтобы пользователи могли получить к нему доступ, экспортируйте его через обычные механизмы пакетов.
Обратите внимание, что в обоих случаях, если пользователь выгружает и перезагружает пакет, значение COUNTS
будет потеряно/сброшено.
Я приведу третий вариант на случай, если пользователь хочет/нуждается в прямом доступе или вы не хотите использовать этот тип управления значениями в своем пакете.
Заставьте пользователя предоставить его в любое время. Для этого добавьте аргумент к каждой функции, которая в нем нуждается, и попросите пользователя передать среду. Я рекомендую это, потому что большинство аргументов передаются по значению, но среды позволяют ссылочную семантику (передавать по ссылке).
Например, в вашем пакете:
myfunc <- function(..., countenv) {
stopifnot(is.environment(countenv))
# do something
countenv$COUNT <- countenv$COUNT + 1
}
otherfunc <- function(..., countenv) {
countenv$COUNT <- countenv$COUNT + 1
}
new_countenv <- function(init = 0) {
E <- new.env(parent = emptyenv())
E$COUNT <- init
E
}
где new_countenv
на самом деле просто функция удобства.
Затем пользователь будет использовать ваш пакет как:
mycount <- new_countenv()
myfunc(..., countenv = mycount)
otherfunc(..., countenv = mycount)
Спасибо, что поделились несколькими решениями. Это будет очень полезно, поскольку я продвигаюсь по своему пакету. Ваше здоровье!
Определен ли
COUNTS
буквально в одном изyourpackage/R/*.R
вне определений функций?