R 包:当我的导出函数未显式调用其他包中的函数但子例程调用时 "import" 如何工作

R Package: how "import" works when my exported function does not call explicitly a function from other packages, but a subroutine does

我正在开发我的第一个 R 程序包,关于 DESCRIPTION 文件中的 Imports 有一些我不清楚的地方。我浏览了很多解释包结构的指南,但我没有找到问题的答案,所以这就是我的情况。

因为对 X 的调用是在函数 hidden 内部进行的,所以在我的函数 f 中没有标记 @import X。因此,我将包 X 添加到我的 DESCRIPTION 文件中的 Imports,希望在那里指定相关的依赖项。

但是,当我使用 devtools::document() 时,生成的命名空间不包含 X 的条目。我明白为什么会发生这种情况:解析器只是没有在 f 的 roxygen 注释中找到标志,并且在运行时调用 f 崩溃,因为缺少 X

现在,我可能可以通过在 f 的导入中指定 X 来修复所有问题。但为什么这个机制如此棘手?或者,类似地,为什么我在 DESCRIPTION 中的导入与 NAMESPACE 中的导入不匹配?

我的理解是可以通过三种 "correct" 方式进行导入。 "correct," 我的意思是他们将通过 CRAN 检查并正常运行。您选择哪个选项是平衡各种优势的问题,并且在很大程度上是主观的。

我将在下面使用术语

查看这些选项
  • primary_function 包中您希望导出的函数
  • hidden primary_function
  • 使用的包中未导出的函数
  • thirdpartypkg::blackboxblackboxthirdpartypkg 包的导出函数。

选项 1(无直接导入/显式函数调用)

我认为这是最常见的方法。 thirdpartypkg 在 DESCRIPTION 文件中声明,但在 NAMESPACE 文件中没有从 thirdpartypkg 导入任何内容。在此选项中,必须使用thirdpartypkg::blackbox构造来获得所需的行为。

# DESCRIPTION

Imports: thirdpartypkg

# NAMESPACE
export(primary_function)


#' @name primary_function
#' @export

primary_function <- function(x, y, z){
  # do something here
  hidden(a = y, b = x, z = c)
}

# Unexported function
#' @name hidden

hidden <- function(a, b, c){
  # do something here

  thirdpartypkg::blackbox(a, c)
}

选项 2(直接导入/无显式函数调用)

在此选项中,您直接导入 blackbox 函数。这样做之后,就不再需要使用thirdpartypkg::blackbox;您可以简单地调用 blackbox 就好像它是您包裹的一部分一样。 (从技术上讲,您将它导入了命名空间,因此无需访问另一个命名空间来获取它)

# DESCRIPTION

Imports: thirdpartypkg

# NAMESPACE
export(primary_function)
importFrom(thirdpartypkg, blackbox)


#' @name primary_function
#' @export

primary_function <- function(x, y, z){
  # do something here
  hidden(a = y, b = x, z = c)
}

# Unexported function
#' @name hidden
#' @importFrom thirdpartypkg blackbox

hidden <- function(a, b, c){
  # do something here

  # I CAN USE blackbox HERE AS IF IT WERE PART OF MY PACKAGE
  blackbox(a, c)
}

选项 3(直接导入/显式函数调用)

您的最后一个选项结合了前两个选项并将 blackbox 导入您的命名空间,然后使用 thirdpartypkg::blackbox 构造来利用它。这是 "correct" 在它起作用的意义上。但它可以说是浪费和多余的。

我说它是浪费和多余的原因是,将 blackbox 导入到您的命名空间后,您永远不会使用它。相反,您在 thirdpartypkg 命名空间中使用 blackbox。本质上,blackbox 现在存在于两个命名空间中,但只有其中一个被使用过。这就引出了为什么要复制的问题。

# DESCRIPTION

Imports: thirdpartypkg

# NAMESPACE
export(primary_function)
importFrom(thirdpartypkg, blackbox)


#' @name primary_function
#' @export

primary_function <- function(x, y, z){
  # do something here
  hidden(a = y, b = x, z = c)
}

# Unexported function
#' @name hidden
#' @importFrom thirdpartypkg blackbox

hidden <- function(a, b, c){
  # do something here

  # I CAN USE blackbox HERE AS IF IT WERE PART OF MY PACKAGE
  # EVEN THOUGH I DIDN'T. CONSEQUENTLY, THE blackbox I IMPORTED
  # ISN'T BEING USED.
  thirdpartypkg::blackbox(a, c)
}

注意事项

那么最好使用哪种方法?对此没有一个简单的答案。我会说选项 3 可能不是可采用的方法。我可以告诉你,Wickham 建议反对选项 3(我一直在该框架下开发,他建议我反对)。

如果我们在选项一和选项二之间做选择,我们要考虑的是1)写代码的效率,2)阅读代码的效率,3)执行代码的效率。

当谈到编写代码的效率时,@importFrom thirdpartypkg blackbox 通常更容易,避免使用 :: 运算符。它只是节省了几个击键。但是,这会对代码的可读性产生不利影响,因为现在无法立即看出 blackbox 的来源。

在阅读代码效率方面,省略@importFrom而使用thirdpartypkg::blackbox要好。这使得 blackbox 的来源一目了然。

就执行代码的效率而言,@importFrom更好。调用 thirdpartypkg::blackbox 比使用 @importFrom 和调用 blackbox 慢大约 0.1 毫秒。那不是很多时间,所以可能不是太多的考虑因素。但是,如果您的包使用数百个 :: 构造,然后被投入循环或重采样过程,那么这些毫秒数可能会开始增加。

最后,我认为我读过的最好的指导(我不知道在哪里)是,如果你要多次调用 blackbox,那么值得使用 @importFrom。如果您只会在一个包中调用它三到四次,请继续使用 :: 结构。