在重组我的代码库时,我想清理我的代码共享机制。到目前为止,我正在使用source
来处理许多小型的,基本上是独立的功能模块。
然而,这种方法存在许多问题,其中包括
source
链),chdir=TRUE
参数,硬编码路径),理想情况下,我希望得到类似于Python模块机制的东西。 R包机制在这里有点过分:我不想要生成嵌套路径层次结构,多个文件包含大量元数据并手动构建包只是为了得到一个小的,自包含,可重用的代码模块。
现在我正在使用一个代码片段,它允许我解决上面提到的前两个问题。包含的语法如下:
import(functional)
import(io)
import(strings)
...并且模块被定义为驻留在本地路径中的简单源文件。 The definition of import
is straightforward但我无法解决第三点:我想将模块导入一个单独的命名空间,但从我看到的命名空间查找机制非常硬连接到包。是的,我可以覆盖`::`
或getExportedValue
,也许asNamespace
和isNamespace
,但这种感觉很脏,有可能打破其他包裹。
答案 0 :(得分:15)
康拉德,严肃地说,是对需求的回答
获得一个小的,独立的,可重用的代码模块
是来创建一个包。这个福音在SO和其他地方重复了很多次次。实际上,您可以使用最少的模糊创建最小的包。
此外,运行后
setwd("/tmp")
package.skeleton("konrad")
并删除一个临时文件,我留下了
edd@max:/tmp$ tree konrad/
konrad/
├── DESCRIPTION
├── man
│ └── konrad-package.Rd
└── NAMESPACE
1 directory, 3 files
edd@max:/tmp$
真的那是不是很麻烦?
答案 1 :(得分:14)
这是一个完全自动化包创建,编译和重新加载的函数。正如其他人所指出的那样,实用程序函数package.skeleton()
和devtools::load_all()
已经让你几乎一直在那里。这只是结合了它们的功能,使用package.skeleton()
在临时目录中创建源目录,当load_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."
答案 2 :(得分:13)
包只是存储文件位置的惯例(R/
中的R文件,man/
中的文档,src
中的编译代码,data/
中的数据):如果你有超过一些文件,你最好坚持已建立的约定。换句话说,使用包比不使用包更容易,因为您不需要考虑:您可以利用现有的约定,每个R用户都会理解正在发生的事情。
所有最小的包真正需要的是一个DESCRIPTION
文件,它说明了包的功能,谁可以使用它(许可证),以及如果有问题需要联系谁(维护者)。这有点开销,但并不重要。一旦你写完了,你就可以根据需要填写其他目录 - 不需要笨拙的package.skeleton()
。
也就是说,使用软件包的内置工具很麻烦 - 你必须重新构建/重新安装软件包,重新启动R并重新加载软件包。这就是devtools::load_all()
和Rstudio的构建和放大的地方。重新加载 - 它们对包使用相同的规范,但提供了从源更新包的更简单方法。您当然可以使用其他答案提供的代码片段,但为什么不使用经过数百个(好的,至少数十个)R开发人员使用的经过良好测试的代码?
答案 3 :(得分:7)
我对OP问题的评论不太正确,但我认为重新编写import
函数就可以了。 foo.R
和bar.R
是当前工作目录中包含单个函数(baz
)的文件,用于打印下面显示的输出。
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"
请注意,baz()
本身不会被找到,但OP似乎还是希望明确::
。
答案 4 :(得分:6)
我完全同情@Dirk的回答。制作最小包装所涉及的小开销似乎值得符合“标准方式”。
然而,我想到的一件事是source
的{{1}}参数,让你可以使用local
,你可以像命名空间一样使用它,例如。
environment
要使用简单的语法访问这些导入的环境,请为这些导入环境提供一个新类(例如,“导入”),并使assign(module, new.env(parent=baseenv()), envir=topenv())
source(filename, local=get(module, topenv()), chdir = TRUE)
通用,默认为::
getExportedValue
不存在。
pkg
下面是一个更安全的选项,可以防止加载包中包含与使用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)
}
访问的包同名的对象时出错。
::
如果您加载了'::' <- 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)
}
,并且随后尝试使用data.table
访问其中一个对象,则会产生正确的结果。
答案 5 :(得分:4)
我已经实施了一个全面的解决方案,并将其作为一个包‹modules›发布。
在内部,模块使用类似于包的方法;即,it loads the code inside a dedicated namespace environment然后exports (= copies) selected symbols into a module environment返回给用户,并可选择附加。
该软件包的使用情况在其网站上有详细描述。