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 有一些我不清楚的地方。我浏览了很多解释包结构的指南,但我没有找到问题的答案,所以这就是我的情况。
- 我定义了一个我将导出的函数
f
,因此它的定义将在顶部有适当的 @export
roxygen 注释。
- 现在,我的函数
f
调用了一个我不想导出的子例程 hidden
。函数 hidden
也使用其他包,比如包 X
。
因为对 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::blackbox
,blackbox
是 thirdpartypkg
包的导出函数。
选项 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
。如果您只会在一个包中调用它三到四次,请继续使用 ::
结构。
我正在开发我的第一个 R 程序包,关于 DESCRIPTION 文件中的 Imports 有一些我不清楚的地方。我浏览了很多解释包结构的指南,但我没有找到问题的答案,所以这就是我的情况。
- 我定义了一个我将导出的函数
f
,因此它的定义将在顶部有适当的@export
roxygen 注释。 - 现在,我的函数
f
调用了一个我不想导出的子例程hidden
。函数hidden
也使用其他包,比如包X
。
因为对 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::blackbox
,blackbox
是thirdpartypkg
包的导出函数。
选项 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
。如果您只会在一个包中调用它三到四次,请继续使用 ::
结构。