【问题标题】:What I should return in a function for writing files to disk?我应该在将文件写入磁盘的函数中返回什么?
【发布时间】:2021-12-17 02:27:10
【问题描述】:

我正在制作一个简单的 R 函数,以相同的方式在磁盘上一致地写入文件,但在不同的文件夹中:

library(magrittr)

main_path <- here::here()

write_to_disk <- function(data, folder, name){
     data %>%
     vroom::vroom_write(
          file.path(main_path, folder, paste0(name, ".tsv"))
     )
}

我知道我不一定需要在 R 函数中返回任何内容,但如果我需要,这里的 return() 语句合适吗?

非常感谢

【问题讨论】:

  • return(invisible(NULL)) 或者只是函数体最后一行的invisible(NULL)
  • 替代方案,按照我的优先顺序:(1)返回写入文件的进程生成的输出; (2) 返回数据本身(这是vroom 所做的,顺便说一句); (3) 返回生成的文件名,假设所有内容均已写入且没有错误/警告; (4) 包装您的内部调用以将文件保存在trytryCatch 中,如果没有生成错误,则返回TRUE,否则返回FALSE ...尽管向FALSE 添加一个属性以指示失败理由也会很有用。
  • R 中的 Every 函数返回一些东西;你不需要调用return()函数and generally shouldn’t。在您的情况下,只转发任何vroom_write(不是vroom,当然?)返回似乎很好。换句话说:我会保持原样。
  • 是的,那是 vroom_write,感谢您发现错误

标签: r function return-value


【解决方案1】:

老实说,这很容易受到观点和背景的影响,但有些想法:

  1. 返回原始数据。 这种精神在大多数 tidyverse 动词函数和许多其他包(以及一些在 base R 中)中得到传达。如果您使用%&gt;%|&gt; 管道,这样做可以在您的函数之后处理数据,这可能非常方便。

    write_to_disk <- function(data, folder, name){
      vroom::vroom_write(
        data,
        file.path(folder, paste0(name, ".tsv"))
      )
      data
    }
    

    (您的函数已经隐式执行此操作,因为对 vroom::vroom_write 的调用是函数体中的最后一个表达式。)

  2. 返回文件写入调用的输出。坦率地说,我不太喜欢这个,因为如果你改变了你的包装器正在使用哪个函数,那么返回值您的功能很可能会发生变化。我不知道你的包装函数的生命周期预期,但想象一下如果你选择从vroom::vroom 切换到另一个函数; vroom 返回基于 col_select 的数据子集,可能较新的函数会返回整个数据,这可能会破坏下游处理的假设。

    write_to_disk <- function(data, folder, name){
      out <- vroom::vroom_write(
        data,
        file.path(folder, paste0(name, ".tsv"))
      )
      out
    }
    

    注意:我明确选择捕获到out 并返回它,以防您在vroom::vroom_write 和后续out 之间添加代码。您未更改的原始功能实际上是在执行相同的操作,但如果您选择在vroom_write 之后执行任何操作,则需要执行此额外步骤。

    否则,您的函数已经隐式执行此操作,因为 vroom::vroom_write 返回数据。

  3. 返回文件名。这仅在文件名不一定事先知道的情况下才有用。例如,如果您的包装器注意不覆盖同名文件,它可能会添加一个计数器(预扩展),以便永远不会发生覆盖。在这种情况下,调用环境不知道选择的文件名是什么,因此它具有价值(有时)。

    write_to_disk <- function(data, folder, name){
      # file numbering
      re <- paste0("^", name, "_?([0-9]+)?\\.tsv$")
      existfiles <- list.files(folder, pattern = re, full.names = TRUE)
      nextnum <- max(0L, suppressWarnings(as.integer(gsub(re, "\\1", basename(existfiles)))), na.rm = TRUE)
      if (nextnum > 0) {
        name <- sprintf("%s_%03i", name, nextnum + 1L)
      }
      filename <- file.path(folder, paste0(name, ".tsv"))
      vroom::vroom_write(
        data,
        filename
      )
      filename
    }
    

    (提供的“文件编号”代码仅作为我认为返回文件名可能有意义的示例。)

  4. 返回写入函数的成功这可能需要使用trytryCatch(或任何tidyverse 等效项),捕获错误,以及做出相应的反应。

    write_to_disk <- function(data, folder, name){
      res <- tryCatch(
        vroom::vroom_write(
          data,
          file.path(folder, paste0(name, ".tsv"))
        ),
        error = function(e) e
      )
      out <- !inherits(res, "error")
      if (!out) {
        attr(out, "error") <- conditionMessage(res)
      }
      out
    }
    
  5. 返回nothing这当然是最简单的。您需要明确地执行此操作,以免无意中从文件写入函数返回返回值。

    write_to_disk <- function(data, folder, name){
      vroom::vroom_write(
        data,
        file.path(folder, paste0(name, ".tsv"))
      )
      NULL
    }
    

注意事项:

  1. 您对main_path 的使用是函数式编程的,因为基于在其直接范围之外的某些东西的存在,在给定相同输入的情况下,该函数的行为会有所不同。我认为最好传递 write_to_dist(x, file.path(main_path, folder), "somename"),因为 main_path 是在 that 环境中定义的(不在函数内),并且您的函数足够通用,不需要正确定义该变量。 p>

    我已更新上述所有代码以反映这一良好做法。如果您对此感到强烈反对,请随时在您喜欢的位置添加main_path

  2. 不可见地返回上述任何内容可能很有用,这样(例如)保存较大的data.frame 而不捕获其返回值不会使控制台充满数据。使用invisible(data) 很容易做到这一点,并且不会改变任何返回值(除了默认情况下它不会打印在控制台上)。

  3. 仅供参考:康拉德和我在 cmets 中反复讨论return(.) 是否是一个好主意。我不同意大多数说法,并且认为它可能与风格和观点一样多,而不是其他方面。无论如何,由于我的大部分论点 for return 在上述所有代码中都没有实际意义,为了简洁起见,我将其删除。

【讨论】:

  • OP 不必更改他们的代码来执行相同的选项 (1)/(2):vroom_write已经 不可见地返回数据。
  • 不,OP 在他们的问题中犯了一个错误,他们使用的是vroom_write,而不是vroom
  • 好的,那么让我调整一下我的评论:因为R中的每个函数都会返回一个值,这就是众所周知的基本属性 R,我不同意(相当强烈地)你关于使用return 更明确的说法。它更详细,但甚至没有更明确一点。出于这个原因,我建议不要这样做。
  • 我承认我的许多函数都使用 implicit last-expression-returned 思维方式,但是零异常情况下“不应该”的声明近乎双曲线。 “一般”开始了这个讨论,但确实有明显的例子(再次,在我看来)通过明确的return 声明得到了很大的改进(可读性、可维护性、不易出错)。 (是的,这是一种观点,但我认为将其称为“R 最佳实践”(而不是您的话)太过分了。)
  • 底线,@KonradRudolph,我认为在 80% 的情况下,显式调用 return 是不必要的……但原始函数调用引起的开销是微不足道的,在某些情况下,它可能会使函数编写者的意图更加清晰。感谢您的讨论 :-) 您的意见没有错 :-)
猜你喜欢
  • 2013-05-25
  • 1970-01-01
  • 2019-09-18
  • 2016-01-12
  • 2021-01-13
  • 1970-01-01
  • 2011-06-15
  • 1970-01-01
  • 2012-11-01
相关资源
最近更新 更多