Функции замены в R, которые не принимают ввод - PullRequest
1 голос
/ 04 февраля 2020

Похоже, что это связано с несколькими другими вопросами, которые были заданы (например, с этим ), но я не могу понять, как сделать именно то, что я хочу. Возможно, функции замены - это неправильный инструмент для работы, что также будет вполне приемлемым ответом. Я гораздо лучше знаком с Python, чем с R, и я легко могу понять, как я хочу это сделать в Python, но я не могу понять, как подойти к нему в R.

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

Более конкретно, у меня есть список (технически это класс s3, но я не думаю, что это действительно имеет отношение к этой проблеме), что содержит некоторые вещи, относящиеся к процессу, запущенному с помощью processx::process$new() call. Для воспроизводимости вот скрипт игрушечной оболочки, который вы можете запустить, и код для получения моего 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"  

Итак, специфическая проблема c здесь немного присуща process$new, но я думаю, что общий принцип важен. Я пытаюсь собрать все выходные данные этого процесса после его завершения, но вы можете вызвать process$new$read_all_output_lines() (или его родственные функции) только один раз, потому что в первый раз он вернет результат из буфера, а в последующие раз он ничего не вернет. , Кроме того, я собираюсь вызвать их несколько, а затем вернуться, чтобы «проверить их», поэтому я не могу просто сразу вызвать res$process$read_all_output_lines(), потому что тогда он будет ждать окончания процесса до sh, прежде чем функция вернется. , что не , что я хочу.

Поэтому я пытаюсь сохранить результат этого вызова в 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 ]

1 голос
/ 04 февраля 2020

После получения дополнительной информации от 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, если процесс завершится; иначе это оставляет это в покое. В любом случае он распечатывает текущее содержимое. Если это клиентский код, вам понадобится дополнительная логика проверки типа c для переданного объекта.

Так что я могу сделать

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."
1 голос
/ 04 февраля 2020

Возможно, я что-то здесь упускаю, но почему бы вам просто не записать вывод после создания объекта, чтобы он был там при первом возврате функции?

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