简体   繁体   English

在不引入依赖关系的情况下,在 R package 中定义 S3 方法的首选方法是什么?

[英]What's the preferred means for defining an S3 method in an R package without introducing a dependency?

I have an R package (not currently on CRAN) which defines a couple of S3 methods of generic functions from other packages (specifically knitr::knit_print and huxtable::as_huxtable ).我有一个 R package (目前不在 CRAN 上),它定义了来自其他包(特别是knitr::knit_printhuxtable::as_huxtable )的几个通用函数的 S3 方法。 However, they're not a key part of my package, so I'd prefer not to create a dependency on those packages when a user installs my package.但是,它们不是我的 package 的关键部分,所以当用户安装我的 package 时,我不希望创建对这些包的依赖。 Up until R 4.0.0, I exported the S3 methods without importing the generics.在 R 4.0.0 之前,我在没有导入 generics 的情况下导出了 S3 方法。 Using roxygen2 , my @export directive was translated into an export() directive in NAMESPACE rather than S3method() .使用roxygen2 ,我的@export指令被转换为 NAMESPACE 中的export()指令,而不是S3method() This worked fine in R versions < 4.0.0 because R looks in the global environment for a matching generic_function.class method first rather than relying on proper registration of the S3 method.这在 R 版本 < 4.0.0 中运行良好,因为 R 在全局环境中查找匹配的generic_function.class方法首先而不是依赖于 S 方法的正确注册。 However, as per this blog on developer.r-project.org , R no longer looks for non-registered S3 methods.但是,根据developer.r-project.org 上的这篇博客,R 不再寻找未注册的 S3 方法。

What is the best way round this?解决这个问题的最好方法是什么? For now, I've added @importFrom directives to my roxygen2 blocks and have added both packages to the imports section of DESCRIPTION.现在,我已将@importFrom指令添加到我的roxygen2块中,并将这两个包添加到说明的导入部分。 However, as I understand things this will mean any user installing my package will then also have to install knitr and huxtable whether they want to or not.但是,据我了解,这意味着任何安装我的 package 的用户也必须安装knitrhuxtable ,无论他们是否愿意。

Fortunately, for R >= 3.6.0, you don't even need the answer by caldwellst .幸运的是,对于 R >= 3.6.0,您甚至不需要caldwellst 的答案 From the blog entry you linked above:从您在上面链接的博客条目中:

Since R 3.6.0, S3method() directives in NAMESPACE can also be used to perform delayed S3 method registration.由于 R 3.6.0,NAMESPACE 中的 S3method() 指令也可用于执行延迟的 S3 方法注册。 With S3method(PKG::GEN, CLS, FUN) function FUN will get registered as an S3 method for class CLS and generic GEN from package PKG only when the namespace of PKG is loaded.使用 S3method(PKG::GEN, CLS, FUN) function FUN 将注册为 class CLS 和通用 GEN 的 class 的 S3 方法和来自 ZEFE90A8E604A7C840E88D03A67F 的通用 GEN 仅当 GDPPK6 的命名空间被加载时。 This can be employed to deal with situations where the method is not “immediately” needed, and having to pre-load the namespace of pkg (and all its strong dependencies) in order to perform immediate registration is considered too “costly”.这可以用于处理不“立即”需要该方法的情况,并且必须预加载 pkg 的命名空间(及其所有强依赖项)以执行立即注册被认为过于“昂贵”。

Additionally, this is also discussed in the docs for the other suggestion of vctrs::s3_register() :此外,这也在文档中讨论了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)
#' }

So, you would simply need to not use @importFrom and instead of @export , use @exportS3Method package::generic (See https://github.com/r-lib/roxygen2/issues/796 and https://github.com/r-lib/roxygen2/commit/843432ddc05bc2dabc9b5b22c1ae7de507a00508 ) So, you would simply need to not use @importFrom and instead of @export , use @exportS3Method package::generic (See https://github.com/r-lib/roxygen2/issues/796 and https://github. com/r-lib/roxygen2/commit/843432ddc05bc2dabc9b5b22c1ae7de507a00508

Illustration插图

So, to illustrate, we can make two very simple packages, foo and bar .因此,为了说明,我们可以制作两个非常简单的包, foobar The package foo just has a generic foo() function and default method: package foo仅具有通用foo() function 和默认方法:

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.")
}

After document() and install() ing, we create bar :document()install()之后,我们创建bar

create_package("bar")

which creates a bar method for foo() :它为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.")
}

Importantly, we must load the foo package before running document() , or @exportS3Method won't work.重要的是,我们必须在运行document()之前加载foo package ,否则@exportS3Method将不起作用。 That is,那是,

library(foo)
document()

But, if we do that, we get the following in the NAMESPACE for bar :但是,如果我们这样做,我们会在barNAMESPACE中得到以下内容:

# Generated by roxygen2: do not edit by hand

S3method(foo::foo,bar)

We have to manually add foo to "Suggests" in DESCRIPTION .我们必须手动将foo添加到DESCRIPTION中的“Suggests”中。

Then if we uninstall foo , we can still install bar :然后如果我们卸载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)

The vctrs package provides a function called s3_register that dynamically registers methods for use in the .onLoad function. vctrs package 提供了一个名为s3_register的 function,它动态注册用于.onLoad function 的方法。 You can read more about its use here , for yourself you would want:您可以在此处阅读有关其使用的更多信息,您自己会想要:

.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")
  }
}

The documentation is kind as well so you don't have to import vctrs :该文档也很友好,因此您不必导入vctrs

To avoid taking a dependency on vctrs for this one function, please feel free to copy and paste the function source into your own package.为了避免这个 function 依赖于 vctrs,请随意将 function 源复制并粘贴到您自己的 package 中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM