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