【问题标题】:Finding and writing environment寻找和写作环境
【发布时间】:2020-12-05 05:56:45
【问题描述】:

我正在编写一个等效于 Python 的“pop”方法的 R。我知道第 99 个百分位有一个,但我更喜欢我自己的(实践/理解/一致性等)。 作为参考,pop() 接受一个对象并从对象中删除第一项,同时返回它。所以

> l <- c(1,3,5)
> x <- pop(l)
> print(l)
> 3, 5
> print(x)
> 1

我正在使用 assign() 将输入对象替换为减去第一个值并从函数返回所述第一个值。

我的问题是,如何获取输入对象的环境并在 assign() 中使用该环境? 我尝试使用返回“R_GlobalEnv”的pryr::where(),但我不能在assign() 中使用这个值。相反,我可以在 assign() 中工作的唯一值是 'globalenv()'。

从手机发布,如果有问题请告诉我。

【问题讨论】:

标签: r environment assign


【解决方案1】:

您可以在基础 R 中实现此功能,但不建议这样做。 R 是一种函数式语言,最终用户不期望具有副作用的函数。

pop <- function(vec)
{
  vec_name <- deparse(substitute(vec))
  assign(vec_name, vec[-1], envir = parent.frame())
  vec[1]
}

a <- c(2, 7, 9)

a
#> [1] 2 7 9

pop(a)
#> [1] 2

a
#> [1] 7 9

pop(a)
#> [1] 7

a
#> [1] 9

reprex package (v0.3.0) 于 2020-08-15 创建

【讨论】:

  • 我刚刚编辑了我的答案:如果你能找到一种方法来处理我在那里描述的间接流行音乐,我会很感兴趣。
  • 标记为正确,因为它获得了我所追求的环境。你是对的,这不是用户对 functional R 的期望,但是它被用于具有 R6 OOP 框架的程序中。添加了一条消息,让用户知道原始对象已更改。谢谢!
【解决方案2】:

下面的答案是基于这个R-Help post,函数pop和来自this SO post的函数getEnvOf,都适应了问题的问题。

getEnvOf <- function(what, which=rev(sys.parents())) {
  what <- as.character(substitute(what))
  for (frame in which)
    if (exists(what, frame=frame, inherits=FALSE))
      return(sys.frame(frame))
  return(NULL)
}
pop <- function(x){
  y <- as.character(substitute(x))
  e <- getEnvOf(y)
  if(length(x) > 0) {
    val <- x[[length(x)]]
    assign(y, x[-length(x)], envir = parent.env(e))
    val
  } else {
    msg <- paste(sQuote(y), "length is not > 0")
    warning(msg)
    NULL
  }
}

y <- c(1,3,5)
pop(y)

这也适用于列表。

z <- list(1, 2, 5)
pop(z)

w <- list(1, c(2, 4, 6), 5)
pop(w)
#[1] 5

pop(w)
#[1] 2 4 6

pop(w)
#[1] 1

pop(w)
#NULL
#Warning message:
#In pop(w) : ‘w’ length is not > 0

【讨论】:

  • 我刚刚编辑了我的答案:如果你能找到一种方法来处理我在那里描述的间接流行音乐,我会很感兴趣。
【解决方案3】:

您可以使用pryr::promise_info(l)$env 来完成此操作,但这是一件非常不像 R 的事情。函数不应该有副作用。

例如,

    pop <- function(l) {
      info <- pryr::promise_info(l)
      if (!is.name(info$code))
        stop("Argument expression should be a name.")
      result <- l[[1]]  # work on lists too
      assign(as.character(info$code), l[-1], envir = info$env)
      result
    }
    l <- c(1, 3, 5)
    pop(l)
#> Registered S3 method overwritten by 'pryr':
#>   method      from
#>   print.bytes Rcpp
#> [1] 1
    l
#> [1] 3 5

reprex package (v0.3.0) 于 2020-08-15 创建

编辑添加:有趣的是,到目前为止,三个答案中没有一个适用于像这样的复杂情况:

f <- function(x) {
  cat("The pop(x) result is", pop(x), "\n")
  cat("Now x is ", x, "\n")
  cat("Now l is ", l, "\n")
}

l <- c(1, 3, 5)
f(l)

@RuiBarradas 的回答给出了

The pop(x) result is 5 
Now x is  1 3 5 
Now l is  1 3 5 

(他弹出最后一个值而不是第一个值,这没什么大不了,但xl都没有被修改。)

@AllanCameron 的回答给出了

The pop(x) result is 1 
Now x is  3 5 
Now l is  1 3 5 

这可以说是正确的(x 被弹出),但我认为 l 被弹出会很好,但这似乎很棘手。

我的回答随着这条消息而死:

Error in pop(x) : Argument expression should be a name.

这似乎是一个错误:显然无论是 x 还是 l,它确实是一个名称。问题似乎出在pryr::promise_info 中,它返回的编译代码将返回x 的值,而不仅仅是x 的代码。如果我通过compiler::enableJIT(0) 关闭 JIT 编译,我会得到与@AllanCameron 相同的结果。我不清楚如何放松适当的数量以弹出l 而不仅仅是x

【讨论】:

  • 我认为我不会对函数 f 弹出原始 l 感到太高兴。如果我们想要一个使用pop 作为某种算法的一部分的函数来处理向量以从中生成新结果怎么办?我们会破坏我们的初始向量。例如,我们无法将数据框列传递给它。这就是为什么我不会使用pop 的原因,如果我这样做了,它只会供内部使用,并且不会覆盖高于第一个父框架的任何内容。所以我认为你表现出的行为是理想的和正确的。这是一项功能,而不是错误 ;)
  • 显然f 这个名字的信息量不是很大,但是如果它是一个名为showThenPop 的函数,在弹出之前打印了它的参数呢?在这种情况下,您希望 pop 应用于 l,但您无法使用我们的 pop 函数实现来实现,您需要将 pop 的主体复制到 showThenPop。基本上pop 都是关于通过引用传递的,而且似乎很难伪造通过引用传递两个级别。
  • 如果您真的想这样做,您可以在父框架中将x 重写为l 的外部函数中使用相同的技巧。这再次允许您将您的操作限制在父框架​​中。没有必要让它变得更复杂。
  • 这对于其他任何人来说真的很有用,我已经将另一个答案标记为正确,因为它为我的具体情况提供了解决方案,但如上所述,它不是用于功能用途。把这个放在这里让其他人可以看到问题真的很有用,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
相关资源
最近更新 更多