没有包的命名空间

时间:2013-04-03 13:43:23

标签: r module namespaces

在重组我的代码库时,我想清理我的代码共享机制。到目前为止,我正在使用source来处理许多小型的,基本上是独立的功能模块。

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

  • 缺乏圆形度测试(意外圆形source链),
  • 正确指定包含路径(chdir=TRUE参数,硬编码路径),
  • 所需的复杂语法
  • 名称冲突的可能性(重新定义对象时)。

理想情况下,我希望得到类似于Python模块机制的东西。 R包机制在这里有点过分:想要生成嵌套路径层次结构,多个文件包含大量元数据并手动构建包只是为了得到一个小的,自包含,可重用的代码模块。

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

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

...并且模块被定义为驻留在本地路径中的简单源文件。 The definition of import is straightforward但我无法解决第三点:我想将模块导入一个单独的命名空间,但从我看到的命名空间查找机制非常硬连接到包。是的,我可以覆盖`::`getExportedValue,也许asNamespaceisNamespace,但这种感觉很脏,有可能打破其他包裹。

6 个答案:

答案 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.Rbar.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返回给用户,并可选择附加。

该软件包的使用情况在其网站上有详细描述。