Тонкое разное поведение между() и attach() в R?
Люди часто используют функции attach()
и detach()
для настройки "путей поиска" для имен переменных в R, но поскольку это изменяет глобальное состояние, которое трудно отслеживать, люди рекомендуют вместо with()
, которая устанавливает временное изменение пути поиска для продолжительности одного выражения.
Однако я только заметил, что в отличие от attach()
, with()
, по-видимому, не выполняет "разрешить" функции. Например, сначала настройте фиктивную функцию, которая будет обращаться к переменной с именем x
:
f <- function { print(x) }
Теперь,
with(list(x=42), f())
не удается, даже если
with(list(x=42), print(x))
и
attach(list(x=42))
f()
оба успеха!: (
Может ли кто-нибудь сказать мне, почему? Я бы хотел, чтобы with()
вел себя точно так же, как attach()
здесь, чтобы я мог эффективно передать большой список параметров функции, настроив среду, содержащую значения параметров, с помощью with()
. Я рассматриваю этот подход как имеющее несколько преимуществ по сравнению с альтернативами (два из них, которые я рассмотрел, (а) кропотливо передают все параметры в функцию и (б) явно передают в список/кадр параметров как аргумент функции и имеют сама функция вызывает with()
), но она не работает. Я нахожу это несоответствие довольно тревожным, чтобы быть честным! Любое объяснение/помощь будут оценены.
Я использую R 2.11.1.
Ответы
Ответ 1
Разница между тем, что делает with(list(x = 42), f())
, и то, что вы ожидаете, - это разница между лексическим охватом (что и использует R) и динамическим охватом (что, кажется, то, что вы ожидаете).
Лексическая область видимости означает, что свободные переменные (например, переменная x
в f
) просматриваются в среде, где f
определен, а не из среды f
вызывается из.
f
определяется в глобальной среде, так что там, где x
просматривается.
Не имеет значения, что with
был вызван для создания новой среды, из которой вызывается f
, поскольку среда, из которой ее вызов не участвует в поиске свободных переменных.
Чтобы заставить это работать так, как вы хотите создать копию f
и reset своей среды, поскольку это то, что R использует для поиска свободных переменных:
with(list(x = 42), { environment(f) <- environment(); f() })
По общему признанию, это немного обременительно, но вы можете немного упростить его с помощью прото-пакета, поскольку proto
сбрасывает среду каждой функции, которая явно вставлена в прото-объект:
library(proto)
with(proto(x = 42, f = f), f())
ДОБАВЛЕНО:
Обратите внимание, что если ваша цель состоит в том, чтобы делать объектно-ориентированное программирование (в соответствии с вашим комментарием к другому ответу), вы можете захотеть заглянуть в proto далее в proto домашняя страница. Например, мы могли бы определить прото-объект p
и переопределить f
так, чтобы его метод p
(в этом случае он должен принять объект в аргументе 1) следующим образом:
library(proto)
p <- proto(x = 42, f = function(.) print(.$x))
p$f()
ДОБАВЛЕНО 2:
При подключенном случае запуск f()
сначала выполняется в глобальной среде, так как здесь определяется f
. Поскольку x
не найден в глобальной среде, он смотрит на родителя глобальной среды, и в этом случае он находит его там. Мы можем обнаружить родителя глобальной среды с помощью parent.env
, и здесь мы видим, что присоединенная среда стала родительским элементом глобальной среды.
> attach(list(x = 42))
> parent.env(.GlobalEnv)
<environment: 0x048dcdb4>
attr(,"name")
[1] "list(x = 42)"
Мы можем просмотреть глобальную среду и всех ее предков в следующем порядке:
> search()
[1] ".GlobalEnv" "list(x = 42)" "package:stats"
[4] "package:graphics" "package:grDevices" "package:utils"
[7] "package:datasets" "package:methods" "Autoloads"
[10] "package:base"
Таким образом, "list(x = 42)"
является родительским элементом глобальной среды, stats является родительским элементом "list(x = 42)"
и т.д.
Ответ 2
Я думаю, это связано с тем, что вы не определяете какие-либо аргументы для f
, а оттуда как требуется поиск x
для print(x)
.
В обычном режиме f()
будет искать x
в глобальной среде, если он не указан (и он не является и не может быть таким, как f
не принимает никаких аргументов).
Внутри with()
происходит то, что любые аргументы, необходимые для f
, будут взяты из аргумента data
. Но поскольку ваш f
не принимает никаких аргументов, x
в списке никогда не используется. Вместо этого R возвращается к обычному поведению и ищет x
в среде f
, глобальной среды, и, конечно же, ее нет. Разница с attach()
заключается в том, что она явно добавляет объект, содержащий x
к пути поиска, который находится в глобальной среде.
Если вы правильно напишете свою функцию, следуя мантре, которую вы передаете любым и всем аргументам, которые вы используете в функции, тогда все работает так, как можно было бы ожидать:
> F <- function(x) print(x)
> with(list(x = 42), F(x))
[1] 42
> ls()
[1] "f" "F"
Если у вас уже есть список или аргументы, необходимые для вызова, возможно, рассмотрите do.call()
, чтобы настроить вызов для вас, вместо использования с. Например:
> do.call(F, list(x = 42))
[1] 42
Вам все еще нужно, чтобы ваша функция была правильно определена с помощью аргументов, так как ваш f
не работает:
> do.call(f, list(x = 42))
Error in function () : unused argument(s) (x = 42)
Ответ 3
В описании из ?with
указано:
Оцените выражение R в среде, построенной из данных, возможно изменение исходных данных.
Это означает, что функция запускается в среде, где данные существуют, но среда не находится в пути поиска. Таким образом, любые функции, запущенные в этой среде, не будут находить данные, которые не передаются им (поскольку они запускаются в их собственной среде), если не указано, что нужно смотреть на parent.frame
. Рассмотрим следующее:
> f <- function() print(x)
> f()
Error in print(x) : object 'x' not found
> with(list(x=42),f())
Error in print(x) : object 'x' not found
> x <- 13
> f()
[1] 13
> with(list(x=42),f())
[1] 13
> f2 <- function(x) print(get("x",parent.frame()))
> f2()
[1] 13
> with(list(x=42),f2())
[1] 42
Чтобы прояснить, как обычно используется with
, данные передаются функции в среде with
, и обычно вызываемая функция обычно не называется глобальной.
Ответ 4
Список параметров передачи работает для меня:
f <- function(params) with(params, {
print(x)
})
f(list(x=42))
# [1] 42
Но вы должны рассмотреть явные рефренсы, например:
f <- function(params) {
print(params$x)
}
Причина в стадии разработки, с большим количеством переменных, это вопрос времени, когда вы делаете что-то вроде:
f <- function(params) with(params, {
# many lines of code
print(x)
})
x <- 7
f(list(y=8))
# [1] 7 # wasn't in params but you got an answer