Как мне избежать eval и разобрать? - PullRequest
14 голосов
/ 30 марта 2019

Я написал функцию, которая получает исходные файлы, содержащие сценарии для других функций, и сохраняет эти функции в альтернативной среде, чтобы они не загромождали глобальную среду.Код работает, но содержит три экземпляра eval(parse(...)):

# sourceFunctionHidden ---------------------------
# source a function and hide the function from the global environment
sourceFunctionHidden <- function(functions, environment = "env", ...) {
    if (environment %in% search()) {
        while (environment %in% search()) {
            if (!exists("counter", inherits = F)) counter <- 0
            eval(parse(text = paste0("detach(", environment, ")")))
            counter <- counter + 1 
        }
        cat("detached", counter, environment, "s\n")
    } else {cat("no", environment, "attached\n")}
    if (!environment %in% ls(.GlobalEnv, all.names = T)) {
        assign(environment, new.env(), pos = .GlobalEnv)
        cat("created", environment, "\n")
    } else {cat(environment, "already exists\n")}
    sapply(functions, function(func) {
        source(paste0("C:/Users/JT/R/Functions/", func, ".R"))
        eval(parse(text = paste0(environment, "$", func," <- ", func)))
        cat(func, "created in", environment, "\n")
    })
    eval(parse(text = paste0("attach(", environment, ")")))
    cat("attached", environment, "\n\n")
}

Много было написано о неоптимальности конструкции eval(parse(...)) (см. здесь и здесь ).Тем не менее, обсуждения, которые я нашел, в основном касаются альтернативных стратегий для поднабора.Первый и третий экземпляры eval(parse(...)) в моем коде не включают поднаборы (второй экземпляр может быть связан с поднабором).

Есть ли способ вызова new.env(...), [environment name]$[function name] <- [function name] и attach(...) не прибегая к eval(parse(...))?Спасибо.

Примечание: я не хочу менять имена своих функций на .name, чтобы скрыть их в глобальной среде

Ответы [ 3 ]

5 голосов
/ 30 марта 2019

Как бы то ни было, функция source на самом деле использует eval(parse(...)), хотя и несколько тонко. Во-первых, .Internal(parse(...)) используется для создания выражений, которые после дальнейшей обработки передаются в eval. Так что eval(parse(...)) кажется достаточно хорошим для основной команды R в этом случае.

Тем не менее, вам не нужно прыгать через обручи к исходным функциям в новой среде. source предоставляет аргумент local, который может использоваться именно для этого.

local: TRUE, FALSE или среда, определяющая, где анализируются выражения.

Пример:

env = new.env()
source('test.r', local = env)

тестирование работает:

env$test('hello', 'world')
# [1] "hello world"
ls(pattern = 'test')
# character(0)

И пример файла test.r для использования на этом:

test = function(a,b) paste(a,b)
3 голосов
/ 30 марта 2019

Если вы хотите, чтобы он отключался от global_env, поместите его в пакет.Люди в сообществе R обычно помещают кучу часто используемых вспомогательных функций в свой личный пакет.

0 голосов
/ 30 марта 2019

tl; dr: Правильный способ преобразования строк в кавычках в имена объектов - использовать assign() и get().См. этот пост .

Длинный ответ: Ответ @dww о возможности source() напрямую в определенную среду привел меня к изменению второго экземпляраиз eval(parse(...)) следующим образом:

# old version
source(paste0("C:/Users/JT/R/Functions/", func, ".R"))
eval(parse(text = paste0(environment, "$", func," <- ", func)))
# new version
source(
    paste0("C:/Users/JT/R/Functions/", func, ".R"), 
    local = get(environment)
)

Ответ от @dww также заставил меня исследовать attach().attach() имеет аргумент, который позволяет указать среду, в которую следует направлять вывод.Это привело меня к изменению третьего экземпляра eval(parse(...)) (ниже).Обратите внимание на использование get() для преобразования "env", полученного из environment, в env без кавычек, которого требует attach().

# old version
eval(parse(text = paste0("attach(", environment, ")")))
# new version
attach(get(environment), name = environment)

Наконец, в какой-то момент этого процесса я былнапомнил, что rm() имеет аргумент character.only.detach() принимает тот же аргумент, поэтому я изменил второй экземпляр eval(parse()), как показано ниже:

# old version
eval(parse(text = paste0("detach(", environment, ")")))
# new version
detach(environment, character.only = T)

Итак, мой новый код:

# sourceFunctionHidden ---------------------------
# source a function and hide the function from the global environment
sourceFunctionHidden <- function(functions, environment = "env", ...) {
    if (environment %in% search()) {
        while (environment %in% search()) {
            if (!exists("counter", inherits = F)) counter <- 0
            detach(environment, character.only = T)
            counter <- counter + 1 
        }
        cat("detached", counter, environment, "s\n")
    } else {cat("no", environment, "attached\n")}
    if (!environment %in% ls(.GlobalEnv, all.names = T)) {
        assign(environment, new.env(), pos = .GlobalEnv)
        cat("created", environment, "\n")
    } else {cat(environment, "already exists\n")}
    sapply(functions, function(func) {
        source(
            paste0("C:/Users/JT/R/Functions/", func, ".R"), 
            local = get(environment)
        )
        cat(func, "created in", environment, "\n")
    })
    attach(get(environment), name = environment)
    cat("attached", environment, "\n\n")
}
...