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

时间:2020-02-04 16:55:53

标签: r r-s3

这似乎与已经提出的其他几个问题非常相关(例如,this one),但是我无法完全弄清楚该怎么做。也许替换功能是该工作的错误工具,这也将是一个完全可以接受的答案。我对Python的了解比对R的了解要多得多,我可以轻松地想到如何在Python中实现它,但是我不太了解如何在R中实现它。

问题:我试图在函数中修改对象,而不必返回它,但是我不需要传递值会对其进行修改,因为该值是对象中已包含的函数调用的结果。

更具体地说,我有一个列表(从技术上讲是s3类,但我认为实际上与该问题无关),其中包含一些与以processx::process$new()开头的进程有关的东西。为了重现性,您可以运行以下玩具外壳脚本,以及获取我的res对象的代码:

echo '
echo $1
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()(或它的同级函数),因为它第一次会从缓冲区返回结果,而随后的时间会什么也不返回。另外,我将调用其中的一堆,然后返回“检查它们”,因此我不能立即调用res$process$read_all_output_lines(),因为那样一来它将等待过程完成,然后函数返回,这不是我想要的。

因此,我试图将该调用的输出存储在res$output中,然后保留该调用并在后续调用中将其返回。如此... 我需要有一个功能,可以用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中供以后访问,因此后续调用均不返回任何内容。

我也尝试过这样的事情:

`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"这样的作业来调用它。

很明显,我也可以返回修改后的res对象,但是我不喜欢这样做,因为那样的话,用户必须知道要res <- get_output(res),并且如果他们忘记这样做了(第一次),则输出将丢失给以太坊,无法恢复。不好。

非常感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

我可能在这里丢失了一些东西,但是为什么不创建对象后就只写输出,以便在函数第一次返回时就在那儿?

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."

答案 1 :(得分:1)

从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."