【问题标题】:What's the preferred means for defining an S3 method in an R package without introducing a dependency?在 R 包中定义 S3 方法而不引入依赖项的首选方法是什么?
【发布时间】:2020-08-12 09:44:29
【问题描述】:

我有一个 R 包(目前不在 CRAN 上),它定义了一些来自其他包(特别是 knitr::knit_printhuxtable::as_huxtable)的通用函数的 S3 方法。但是,它们不是我的包的关键部分,所以我不想在用户安装我的包时创建对这些包的依赖。直到 R 4.0.0,我导出了 S3 方法而不导入泛型。使用roxygen2,我的@export 指令被转换为NAMESPACE 中的export() 指令,而不是S3method()。这在 R 版本 generic_function.class 方法,而不是依赖于 S3 方法的正确注册。但是,根据this blog on developer.r-project.org,R 不再查找未注册的 S3 方法。

解决这个问题的最佳方法是什么?现在,我已将 @importFrom 指令添加到我的 roxygen2 块中,并将这两个包添加到说明的导入部分。但是,据我了解,这意味着任何安装我的软件包的用户都必须安装 knitrhuxtable,无论他们是否愿意。

【问题讨论】:

  • 我必须多考虑一下(也许再参考“Writing R Extensions”),但你可以在@987654334 的“Suggests”中列出它们@文件
  • 嗯...看看Sections 1.1.3 & 1.1.3.1 in Writing R Extensions,似乎这至少是DESCRIPTION中“建议”的预期用途,即使典型的用例是示例等。
  • 谢谢。他们总是在建议中,但是如果我在没有 importFrom 的 NAMESPACE 中使用 S3method 包将无法构建,并且如果我使用 importFrom,包也必须在 Imports 中(除非我遗漏了什么!)
  • 啊,是的,我认为你是对的。抱歉,只是想解决办法

标签: r project roxygen2 r-s3


【解决方案1】:

幸运的是,对于 R >= 3.6.0,您甚至不需要 the answer by caldwellst。从您上面链接的博客条目中:

自 R 3.6.0 起,NAMESPACE 中的 S3method() 指令也可用于执行延迟的 S3 方法注册。使用 S3method(PKG::GEN, CLS, FUN) 函数,只有在加载 PKG 的命名空间时,FUN 才会从包 PKG 中注册为 CLS 类和通用 GEN 的 S3 方法。这可以用于处理“立即”不需要该方法的情况,并且必须预加载 pkg 的命名空间(及其所有强依赖项)以执行立即注册被认为过于“昂贵”。

此外,vctrs::s3_register() 的其他建议的文档中也对此进行了讨论:

#' For R 3.5.0 and later, `s3_register()` is also useful when demonstrating
#' class creation in a vignette, since method lookup no longer always involves
#' the lexical scope. For R 3.6.0 and later, you can achieve a similar effect
#' by using "delayed method registration", i.e. placing the following in your
#' `NAMESPACE` file:
#'
#' ```
#' if (getRversion() >= "3.6.0") {
#'   S3method(package::generic, class)
#' }

因此,您只需要使用@importFrom,而不是@export,而是使用@exportS3Method package::generic(参见https://github.com/r-lib/roxygen2/issues/796https://github.com/r-lib/roxygen2/commit/843432ddc05bc2dabc9b5b22c1ae7de507a00508

插图

所以,为了说明,我们可以制作两个非常简单的包,foobarfoo 包只有一个通用的 foo() 函数和默认方法:

library(devtools)
create_package("foo")

#' foo generic
#'
#' @param x An object
#' @param ... Arguments passed to or from other methods
#' @export
foo <- function(x, ...) {
    UseMethod("foo", x)
}
#' foo default method
#'
#' @param x An object
#' @param ... Arguments passed to or from other methods
#' @export
foo.default <- function(x, ...) {
    print("Called default method for foo.")
}

document()install()ing 之后,我们创建bar

create_package("bar")

foo() 创建一个bar 方法:

#' bar method for foo
#'
#' @param x A bar object
#' @param ... Arguments passed to or from other methods
#'
#' @exportS3Method foo::foo
foo.bar <- function(x, ...) {
    print("Called bar method for foo.")
}

重要的是,在运行document() 之前,我们必须 加载 foo 包,否则@exportS3Method 将不起作用。也就是说,

library(foo)
document()

但是,如果我们这样做,我们会在NAMESPACE 中为bar 获得以下内容:

# Generated by roxygen2: do not edit by hand

S3method(foo::foo,bar)

我们必须手动将foo 添加到DESCRIPTION 中的“建议”中。

那么如果我们卸载foo,我们仍然可以安装bar

> remove.packages("foo")
Removing package from ‘/home/duckmayr/R/x86_64-pc-linux-gnu-library/4.0’
(as ‘lib’ is unspecified)
> install("bar")
✓  checking for file ‘/home/jb/bar/DESCRIPTION’ ...
─  preparing ‘bar’:
✓  checking DESCRIPTION meta-information ...
─  checking for LF line-endings in source and make files and shell scripts
─  checking for empty or unneeded directories
─  building ‘bar_0.0.0.9000.tar.gz’

Running /opt/R/4.0.0/lib/R/bin/R CMD INSTALL \
  /tmp/Rtmp5Xgwqf/bar_0.0.0.9000.tar.gz --install-tests 
* installing to library ‘/home/jb/R/x86_64-pc-linux-gnu-library/4.0’
* installing *source* package ‘bar’ ...
** using staged installation
** R
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (bar)

【讨论】:

    【解决方案2】:

    vctrs 包提供了一个名为s3_register 的函数,该函数动态注册用于.onLoad 函数的方法。你可以阅读更多关于它的使用here,你自己想要:

    .onLoad <- function(...) {
      if (requireNamespace("knitr", quietly = TRUE)) {
        vctrs::s3_register("knitr::knit_print", "class_name")
      }
      if (requireNamespace("huxtable", quietly = TRUE)) {
        vctrs::s3_register("huxtable::as_huxtable", "class_name")
      }
    }
    

    文档也很好,因此您不必导入vctrs

    为避免这个函数依赖于 vctrs,请随意将函数源复制并粘贴到您自己的包中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-09
      • 2015-08-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多