R 没有包的名称空间

R 没有包的名称空间,r,module,namespaces,R,Module,Namespaces,在重新组织我的代码库时,我希望清理我的代码共享机制。到目前为止,我正在使用source实现许多小型的、基本上独立的功能模块 然而,这种方法存在许多问题,其中包括 缺乏循环性测试(偶然循环源链) 正确指定包含路径所需的复杂语法(chdir=TRUE参数,硬编码路径) 名称冲突的可能性(重新定义对象时) 理想情况下,我希望得到类似于Python模块机制的东西。R包机制在这里可能有些过分:我不想生成嵌套的路径层次结构、包含大量元数据的多个文件并手动构建包,而只是为了得到一个小型的、自包含的、可重用

在重新组织我的代码库时,我希望清理我的代码共享机制。到目前为止,我正在使用
source
实现许多小型的、基本上独立的功能模块

然而,这种方法存在许多问题,其中包括

  • 缺乏循环性测试(偶然循环<代码>源链)
  • 正确指定包含路径所需的复杂语法(
    chdir=TRUE
    参数,硬编码路径)
  • 名称冲突的可能性(重新定义对象时)
理想情况下,我希望得到类似于Python模块机制的东西。R包机制在这里可能有些过分:我不想生成嵌套的路径层次结构、包含大量元数据的多个文件并手动构建包,而只是为了得到一个小型的、自包含的、可重用的代码模块

现在,我正在使用一个代码片段,它允许我解决上面提到的前两个问题。包含的语法如下所示:

import(functional)
import(io)
import(strings)

…模块定义为驻留在本地路径中的简单源文件。但我无法解决第三点:我想将模块导入到一个单独的名称空间中,但从我看到的名称空间查找机制与包之间是紧密相连的。诚然,我可以覆盖
`:`
getExportedValue
asNamespace
isNamespace
,但这感觉非常肮脏,有可能破坏其他软件包。

康拉德,非常严肃地说,这是对需求的回答

获取一个小型的、自包含的、可重用的代码模块

就是创建一个包。这福音在这里和其他地方被重复了无数次。实际上,您可以使用最小的模糊创建最小的包

还有,跑完以后,

 setwd("/tmp")
 package.skeleton("konrad")
删除一个临时文件,我就剩下了

 edd@max:/tmp$ tree konrad/
 konrad/
 ├── DESCRIPTION
 ├── man
 │   └── konrad-package.Rd
 └── NAMESPACE

 1 directory, 3 files
 edd@max:/tmp$ 

那真的有那么繁重吗?

我完全赞同@Dirk的回答。制作一个最小的包所涉及的小开销似乎值得遵循“标准方式”

然而,我想到的一件事是
source
local
参数,它允许您在
环境
中进行源代码转换,您可以将其用作名称空间,例如

assign(module, new.env(parent=baseenv()), envir=topenv())
source(filename, local=get(module, topenv()), chdir = TRUE)
要使用简单的语法访问这些导入的环境,请为这些导入环境提供一个新类(例如“import”),并将
设置为泛型,当
pkg
不存在时,默认设置为
getExportedValue

import <- function (module) {
    module <- as.character(substitute(module))
    # Search path handling omitted for simplicity.
    filename <- paste(module, 'R', sep = '.')

    e <- new.env(parent=baseenv())
    class(e) <- 'import'
    assign(module, e, envir=topenv())
    source(filename, local=get(module, topenv()), chdir = TRUE)
}

'::.import' <- function(env, obj) get(as.character(substitute(obj)), env)
'::' <- function(pkg, name) {
    pkg <- as.character(substitute(pkg))
    name <- as.character(substitute(name))
    if (exists(pkg)) UseMethod('::')
    else getExportedValue(pkg, name)
}

这将给出正确的结果,例如,如果您加载
data.table
,然后尝试使用

访问它的一个对象,我对OP的问题的评论不太正确,但我认为重新编写
import
函数就可以做到这一点
foo.R
bar.R
是当前工作目录中的文件,其中包含一个函数(
baz
),该函数打印如下所示的输出

'::' <- function(pkg, name) {
    pkg.chr <- as.character(substitute(pkg))
    name.chr <- as.character(substitute(name))
    if (exists(pkg.chr)) {
        if (class(pkg) == 'import')
            return(get(name.chr, pkg))
    }
    getExportedValue(pkg.chr, name.chr)
}
import <- function (module) {
  module <- as.character(substitute(module))
  # Search path handling omitted for simplicity.
  filename <- paste(module, 'R', sep = '.')
  # create imports environment if it doesn't exist
  if ("imports" %in% search())
    imports <- as.environment(match("imports",search()))
  # otherwise get the imports environment
  else
    imports <- attach(NULL, name="imports")
  if (module %in% ls("imports"))
    return()
  # create a new environment (imports as parent)
  env <- new.env(parent=imports)
  # source file into env
  sys.source(filename, env)
  # ...and assign env to imports as "module name"
  assign(module, env, imports)
}
setwd(".")
import(foo)
import(bar)
foo$baz()
# [1] "Hello World"
bar$baz()
# [1] "Buh Bye"

import这是一个完全自动化包创建、编译和重新加载的函数。正如其他人所注意到的,实用程序函数
package.skeleton()
devtools::load_all()
几乎已经实现了这一目标。这只是结合了它们的功能,使用
package.skeleton()
在临时目录中创建源目录,当
load\u all()
完成处理后,该目录将被清理

您所需要做的就是指向要在函数中读取的源文件,并为包命名:
import()
为您完成其余的工作

import <- function(srcFiles, pkgName) {
    require(devtools)
    dd <- tempdir()
    on.exit(unlink(file.path(dd, pkgName), recursive=TRUE))
    package.skeleton(name=pkgName, path = dd, code_files=srcFiles)
    load_all(file.path(dd, pkgName))
}

## Create a couple of example source files
cat("bar <- function() {print('Hello World')}", file="bar.R")
cat("baz <- function() {print('Goodbye, cruel world.')}", file="baz.R")

## Try it out
import(srcFiles=c("bar.R", "baz.R"), pkgName="foo")

## Check that it worked
head(search())
# [1] ".GlobalEnv"        "package:foo"       "package:devtools"
# [4] "package:stats"     "package:graphics"  "package:grDevices"
bar()
# [1] "Hello World"
foo::baz()
# [1] "Goodbye, cruel world."

import一个包只是一个存储文件的约定(R文件在
R/
中,文档在
man/
中,编译代码在
src
中,数据在
data/
中):如果你有多个文件,你最好坚持既定的约定。换句话说,使用包比不使用包更容易,因为您不需要思考:您可以利用现有的约定,每个R用户都会了解发生了什么

一个最小的软件包真正需要的是一个
说明
文件,该文件说明了软件包的功能、谁可以使用它(许可证)以及如果出现问题要联系谁(维护者)。这是一个有点开销,但它不是主要的。一旦编写完成,您只需根据需要填写其他目录-无需使用笨拙的
package.skeleton()


也就是说,用于处理软件包的内置工具很麻烦——您必须重新构建/重新安装软件包,重新启动R并重新加载软件包。这就是
devtools::load_all()
和Rstudio的build&reload的用武之地——它们对包使用相同的规范,但提供了从源代码更新包的更简单方法。当然,您可以使用其他答案提供的代码片段,但为什么不使用数百名(至少数十名)R开发人员使用的经过良好测试的代码呢?

我已经实现了一个全面的解决方案,并将其作为一个包发布了

在内部,模块使用类似于包的方法;即,然后返回给用户,并可选地附加


该软件包的使用方法在其网站上有详细说明。

@KonradRudolph但如果你只有一个两个函数的文件,那么你就不必担心循环性,也不必太难避免冲突?但说真的,把某样东西变成一个包真的非常简单。没有人说你需要通过所有的CRAN检查(这需要工作)。但是如果你得到了基本的软件包结构,你可以使用devtools软件包中的
load\u all
函数来获得你想要的所有功能,而不需要显式地安装软件包。这么大