在一个包中,通过::调用函数是一个好的实践吗?

58

我正在编写一些R函数,使用了其他包中的一些有用函数,如stringrbase64enc。不调用library(...)require(...)来先加载这些包,而是使用::直接引用我需要的函数,例如stringr::str_match(...),这样做好吗?

总体上,这样做是一个好习惯吗?或者可能会引发什么问题呢?


1
require通常在包中的函数中使用,这个SO帖子很好地区分了它和library之间的区别。如果你确定只需要一个(或两个)包中的函数,则使用::就可以了,但是当命名空间发生冲突时,我只会选择::。还有,不要忘记:::运算符。 - hrbrmstr
1个回答

79
这完全取决于上下文。
如果存在命名空间冲突,即不同包中具有相同名称的函数,则主要需要使用::。当我加载dplyr包时,它提供了一个filter函数,与默认加载在stats包中的filter函数发生冲突并遮蔽了后者。因此,如果我想在加载dplyr之后使用stats版本的函数,则需要使用stats::filter进行调用。
这也为不要加载大量包提供了动力。如果您只需要从一个包中获取一个函数,那么最好使用::而不是加载整个包,特别是如果您知道该包将屏蔽其他要使用的函数。
在文本中,虽然不是代码,但我确实发现::非常有用。键入stats::filter比"从stats包中获取filter函数"更简洁。
从性能角度来看,使用::会有一个(非常)小的代价。R核心开发团队成员Martin Maechler在r-devel邮件列表(2017年9月)上写道:

许多人似乎忘记了每次使用::都是一个R函数调用,并且与仅使用已导入名称相比使用它是低效的。

性能惩罚非常小,大约几微秒,因此只有在需要高度优化的代码时才需要关注。运行使用::的一百万行代码将比不使用::的代码慢一两秒钟。
就可移植性而言,在脚本顶部明确加载软件包很好,因为这使得可以轻松地查看前几行并查看需要哪些软件包,如果需要可以在深入其他任何内容之前安装它们,即在进行长时间处理的一半时,现在无法完成而必须重新开始。 Aside: 有类似的论点认为应该选择library()而不是require()。如果包不存在,Library会导致错误并停止,而Require会发出警告但继续执行。如果你的代码在包不存在情况下有备选计划,请使用if (require(package)) ...,但如果你的代码没有包将会失败,你应该在顶部使用library(package),以便早日清晰明了地失败。

在你自己的包中

一般的解决方案是在DESCRIPTION文件中制作自己的包,从而导入需要在其中使用的其他包。当你的包被安装时,这些包将自动安装,因此你可以在内部使用pkg::fun。或者,在NAMESPACE文件中也导入它们,你可以导入整个包或选择性地导入特定函数,而不需要::。对此有不同的意见。马丁·梅克勒(与上面相同的R-devel来源)说:

个人认为,现在在包中 :: 的使用非常"滥用",特别是在我强烈建议在 NAMESPACE 中使用 importFrom() 的包中,这样所有操作都会在包加载时完成,然后在包源代码中::不再使用。
另一方面,RStudio首席科学家Hadley Wickham在他的R Packages book中说:
“通常情况下,包会在 DESCRIPTION 中列出而不在 NAMESPACE 中列出。实际上,这就是我推荐的做法:在 DESCRIPTION 中列出包以便安装,然后始终使用 pkg::fun() 明确地引用它。除非有充分的理由不这样做,否则最好明确地指定。”
由于两位尊敬的R专家提供了相反的建议,因此我认为您应该选择适合自己的风格,并满足您对清晰度、效率和可维护性的需求。
如果你经常发现自己仅仅使用另一个包中的一个函数,那么你可以复制代码并将其添加到自己的包中。例如,我有一个用于个人使用的包,它从Hmisc包中借用了%nin%函数,因为我认为这是一个很棒的函数,但我很少使用Hmisc中的其他内容。使用roxygen2,可以轻松地添加@author@references来正确归属所借用的函数的代码。在这样做时,请确保软件包许可证是兼容的。

10
在脚本开头使用library(...)的另一个好处是,如果有人在未安装包的情况下尝试source您的文件,则source命令会很早失败(而不是在潜在的长时间数据加载或操作之后才失败)。 - Hugh
2
@BondedDust 在这种情况下,最好使用stopwarning而不是cat - Gregor Thomas
2
如果你正在编写一个包,我认为最好使用::而不是命名空间导入或importsFrom(除非你从其他包中使用了很多函数)。 - hadley
4
是的,它应该在“DESCRIPTION”文件中导入,但不应该在“NAMESPACE”文件中使用import(pkg)或importFrom(pkg,foo)导入(我认为我的原始评论有点混淆)。 - hadley
2
代码的可读性可能会受到影响,如果每个函数调用都以包名为前缀。特别是当一些函数调用作为另一个函数调用的参数时,等等...(在tidyr中很常见),这会使代码变得混乱。 - Dead Vil
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接