R 中不接受输入的替换函数

Replacement functions in R that don't take input

这似乎与已提出的其他几个问题非常相关(例如 this one),但我不太清楚如何做我想做的事情。也许替换功能是这项工作的错误工具,这也是一个完全可以接受的答案。我对 Python 比 R 更熟悉,我可以很容易地想到我想在 Python 中如何做,但我不太清楚如何在 R 中处理它。

问题: 我正在尝试修改函数内的对象,而不必 return 它,但我 不这样做不需要传入修改它的值,因为这个值是对象中已经包含的函数调用的结果。

更具体地说,我有一个列表(从技术上讲它是一个 s3 class,但我认为这实际上与这个问题无关)包含一些与以 [=15= 开始的进程相关的内容] 称呼。为了可重复性,这里有一个玩具 shell 脚本,你可以 运行,以及获取我的 res 对象的代码:

echo '
echo 
sleep 1s
echo "naw 1"
sleep 1s
echo "naw 2"
sleep 1s
echo "naw 3"
sleep 1s
echo "naw 4"
sleep 1s
echo "naw 5"
echo "All done."
' > naw.sh

然后我的包装器是这样的:

run_sh <- function(.args, ...) {
  p <- processx::process$new("sh", .args, ..., stdout = "|", stderr = "2>&1")
  return(list(process = p, orig_args = .args, output = NULL))
}

res <- run_sh(c("naw.sh", "hello"))

res应该看起来像

$process
PROCESS 'sh', running, pid 19882.

$output
NULL

$orig_args
[1] "naw.sh" "hello"  

所以,这里的具体问题对于 process$new 来说有点特殊,但我认为一般原则是相关的。我试图在它完成后收集这个过程的所有输出,但是你只能调用 process$new$read_all_output_lines() (或者它的同级函数)一次,因为第一次它将 return 来自缓冲区的结果和随后的时间 return 什么都没有。此外,我将调用其中的一些然后返回到 "check on them" 所以我不能立即调用 res$process$read_all_output_lines() 因为它会在函数 [= 之前​​等待进程完成72=]s,不是我想要的。

所以我试图将该调用的输出存储在 res$output 中,然后在后续调用中保留它和 return。 Soooo... 我需要一个函数来修改 res 到位 res$output <- res$process$read_all_output_lines()

这是我根据 this 之类的指导尝试的方法,但没有成功。

get_output <- function(.res) {
  # check if process is still alive (as of now, can only get output from finished process)
  if (.res$process$is_alive()) {
    warning(paste0("Process ", .res$process$get_pid(), " is still running. You cannot read the output until it is finished."))
    invisible()
  } else {
    # if output has not been read from buffer, read it
    if (is.null(.res$output)) {
      output <- .res$process$read_all_output_lines()
      update_output(.res) <- output
    }
    # return output
    return(.res$output)
  }
}

`update_output<-` <- function(.res, ..., value) {
  .res$output <- value
  .res
}

调用 get_output(res) 第一次工作,但它 不会 将输出存储在 res$output 中以供稍后访问,因此后续调用 return 没什么。

我也试过这样的:

`get_output2<-` <- function(.res, value) {
  # check if process is still alive (as of now, can only get output from finished process)
  if (.res$process$is_alive()) {
    warning(paste0("Process ", .res$process$get_pid(), " is still running. You cannot read the output until it is finished."))
    .res
  } else {
    # if output has not been read from buffer, read it
    if (is.null(.res$output)) {
      output <- .res$process$read_all_output_lines()
      update_output(.res) <- output
    }
    # return output
    print(value)
    .res
  }
}

这只是丢弃了 value 但这感觉很愚蠢,因为你必须用我讨厌的 get_output(res) <- "fake" 之类的赋值来调用它。

显然我也可以只 return 修改后的 res 对象,但我不喜欢那样,因为这样用户必须知道要做什么 res <- get_output(res) 并且如果他们忘记了这样做(第一次)然后输出会丢失到以太中并且永远无法恢复。不好。

非常感谢任何帮助!

我可能在这里遗漏了一些东西,但是你为什么不在创建对象后只写输出,以便它在函数第一次出现时就在那里returns?

run_sh <- function(.args, ...) 
{
  p <- processx::process$new("sh", .args, ..., stdout = "|", stderr = "2>&1")
  return(list(process = p, orig_args = .args, output = p$read_all_output_lines()))
}

所以现在如果你这样做

res <- run_sh(c("naw.sh", "hello"))

你得到

res
#> $`process`
#> PROCESS 'sh', finished.
#> 
#> $orig_args
#> [1] "naw.sh" "hello" 
#> 
#> $output
#>  [1] "hello"                                    
#>  [2] "naw.sh: line 2: sleep: command not found" 
#>  [3] "naw 1"                                    
#>  [4] "naw.sh: line 4: sleep: command not found" 
#>  [5] "naw 2"                                    
#>  [6] "naw.sh: line 6: sleep: command not found" 
#>  [7] "naw 3"                                    
#>  [8] "naw.sh: line 8: sleep: command not found" 
#>  [9] "naw 4"                                    
#> [10] "naw.sh: line 10: sleep: command not found"
#> [11] "naw 5"                                    
#> [12] "All done."

从 OP 获得更多信息后,似乎需要一种方法来写入调用该函数的环境中的现有变量。这可以通过非标准评估来完成:

check_result <- function(process_list) 
{ 
  # Capture the name of the passed object as a string
  list_name <- deparse(substitute(process_list))

  # Check the object exists in the calling environment
  if(!exists(list_name, envir = parent.frame()))
     stop("Object '", list_name, "' not found")

  # Create a local copy of the passed object in function scope
  copy_of_process_list <- get(list_name, envir = parent.frame())

  # If the process has completed, write its output to the copy
  # and assign the copy to the name of the object in the calling frame
  if(length(copy_of_process_list$process$get_exit_status()) > 0)
  {
    copy_of_process_list$output <- copy_of_process_list$process$read_all_output_lines()
    assign(list_name, copy_of_process_list, envir = parent.frame()) 
  }
  print(copy_of_process_list)
}

如果进程已完成,这将更新 res;否则它不会管它。在任何一种情况下,它都会打印出当前内容。如果这是面向客户端的代码,您将需要对传入的对象进行进一步的类型检查逻辑。

所以我能做到

res <- run_sh(c("naw.sh", "hello"))

并检查 res 的内容 我有:

res
#> $`process`
#> PROCESS 'sh', running, pid 1112.
#> 
#> $orig_args
#> [1] "naw.sh" "hello" 
#> 
#> $output
#> NULL

如果我立即 运行:

check_result(res)
#> $`process`
#> PROCESS 'sh', running, pid 1112.
#> 
#> $orig_args
#> [1] "naw.sh" "hello" 
#> 
#> $output
#> NULL

我们可以看到该过程尚未完成。但是,如果我等几秒钟并再次调用 check_result,我会得到:

check_result(res)
#> $`process`
#> PROCESS 'sh', finished.
#> 
#> $orig_args
#> [1] "naw.sh" "hello" 
#> 
#> $output
#> [1] "hello"     "naw 1"     "naw 2"     "naw 3"     "naw 4"     "naw 5"    
#> [7] "All done."

并且没有明确写入 res,它已通过函数更新:

res
#> $`process`
#> PROCESS 'sh', finished.
#> 
#> $orig_args
#> [1] "naw.sh" "hello" 
#> 
#> $output
#> [1] "hello"     "naw 1"     "naw 2"     "naw 3"     "naw 4"     "naw 5"    
#> [7] "All done."