如何防止功能污染全局命名空间?

时间:2012-10-26 06:12:20

标签: r

我的R项目变得越来越复杂了,我开始寻找一些与Java / C#中的类相同的构造,或者python中的模块,这样我的全局命名空间就不会被那些从来没有过的函数所困扰在一个特定的.r文件之外使用。

所以,我想我的问题是:在多大程度上可以将函数的范围限制在特定的.r文件或类似文件中?

我想我可以将整个.r文件放到一个巨大的函数中,并将函数放在其中,但这会让回声变得混乱:

myfile.r:

myfile <- function() {
    somefunction <- function(a,b,c){}
    anotherfunction <- function(a,b,c){}

    # do some stuff here...
    123
    456
    # ...
}
myfile()

输出:

> source("myfile.r",echo=T)

> myfile <- function() {
+     somefunction <- function(a,b,c){}
+     anotherfunction <- function(a,b,c){}
+ 
+     # do some stuff here...
+     # . .... [TRUNCATED] 

> myfile()
> 

您可以看到,即使我们在echo=T命令中使用source,也不会打印“123”。

我想知道是否还有其他一些更标准的构造,因为将所有内容放在一个单独的函数中听起来不像是真正标准的东西?但也许是这样?此外,如果它意味着echo=T起作用,那么对我来说这是一个明确的奖励。

3 个答案:

答案 0 :(得分:20)

首先,正如@Spacedman所说,你可以通过套餐获得最佳服务,但还有其他选择。

S3方法

R&#39;面向对象&#34;被称为S3。 R代码的大部分代码都使用了这种特殊的范例。这是使plot()适用于各种对象的原因。 plot()是一个通用函数,R核心团队和程序包开发人员可以为plot()编写自己的方法。严格来说,这些方法可能具有plot.foo()之类的名称,其中foo是一个对象类,函数定义了plot()方法。 S3的美妙之处在于,您(几乎)不需要知道或致电plot.foo()您只需使用plot(bar),R就可以根据plot()方法调出哪个bar方法对象类populate()

在您对您的问题的评论中,您提到您有一个函数"crossvalidate",其中包含"prod".r类的方法(实际上),这些方法分别保存在populate <- function(x, ...) { ## add whatever args you want/need UseMethod("populate") } populate.crossvalidate <- function(x, y, z, ...) { ## add args but must those of generic ## function code here } populate.prod <- function(x, y, z, ...) { ## add args but must have those of generic ## function code here } 中}文件。设置它的S3方法是:

bar

给定一些带有"prod"类的对象populate(bar) ,调用

populate()

将导致R调用populate.prod(泛型),然后查找名为bar的函数,因为它是populate.prod()的类。它找到我们的populate(),因此调度该函数将我们最初指定的参数传递给它。

所以你看到你只使用泛型名称而不是完整的函数名称来引用方法。 R为你找出需要调用的方法。

两个x方法可以有非常不同的参数,但是严格来说它们应该与泛型函数具有相同的参数。因此,在上面的示例中,所有方法都应该包含参数...populate()。 (使用公式对象的方法有一个例外,但我们在这里不需要担心。)

包名称空间

自R 2.14.0以来,所有R包都有自己的命名空间,即使包名作者没有提供,但是R中的命名空间已经存在了很长时间。

在您的示例中,我们希望在S3系统中注册.R通用及其两种方法。我们还希望导出通用函数。通常我们不想或不需要导出各个方法。因此,将函数弹出到包源的R文件夹中的NAMESPACE文件中,然后在包源的顶层创建一个名为export(populate) ## export generic S3method(populate, crossvalidate) ## register methods S3method(populate, prod) 的文件,并添加以下语句: / p>

populate()

然后,一旦安装了软件包,您就会注意到可以调用populate.prod()但是如果您尝试通过提示或其他功能中的名称直接调用populate()等,R会抱怨。这是因为单个方法的函数尚未从命名空间导出,因此在其外部不可见。程序包中调用:::的任何函数都可以访问您定义的方法,但程序包外的任何函数或代码都无法查看方法。如果需要,可以使用mypkg:::populate.crossvalidate(foo, bar) 运算符调用非导出函数,即

mypkg

将起作用,其中NAMESPACE是您的包的名称。

说实话,您甚至不需要populate.xxx()文件,因为R会在您安装软件包时自动生成一个文件,一个自动导出所有功能的软件包。这样,您的两个方法将显示为xxx(其中R是特定方法)并将作为S3方法运行。

在“写作R扩展”手册中阅读Section 1 Creating R Packages,了解所涉及的内容的详细信息,但如果您不想要,也不需要做一半,尤其是如果包裹是供您自己使用。只需创建相应的包文件夹(即man.R),将R文件粘贴到.Rd。在添加

man中写一个\name{Misc Functions} \alias{populate} \alias{populate.crossvalidate} \alias{populate.prod} 文件
\alias{}

位于文件顶部。为您拥有的任何其他功能添加sys.source()。然后,您需要构建并安装该软件包。

使用.r

的备选方案

虽然我不能(不能!)真的推荐我在下面提到的作为长期可行的选项,但是有一个替代方案可以让你将功能与个人{{1您最初请求的文件。这是通过使用非命名空间的环境来实现的,并不涉及创建包。

sys.source()函数可用于从.R文件中获取R代码/函数,并在环境中对其进行评估。当您.R文件正在创建/定义函数时,如果您在内部另一个环境中获取它,那么将在那个环境中定义那些函数。默认情况下,它们在标准搜索路径中无法显示,因此populate()中定义的crossvalidate.R函数不会与populate()中定义的prod.R冲突。当你使用两个独立的环境。当您需要使用一组功能时,您可以将环境分配给搜索路径,然后在其中奇迹般地可以看到所有内容,当您完成后,您可以将其分离。附加其他环境,使用它,分离等。或者您可以使用eval()之类的内容安排在特定环境中评估R代码。

就像我说的那样,这不是一个推荐的解决方案,但它会以一种时尚的方式以你描述的方式运作。例如

## two source files that both define the same function
writeLines("populate <- function(x) 1:10", con = "crossvalidate.R")
writeLines("populate <- function(x) letters[1:10]", con = "prod.R")

## create two environments
crossvalidate <- new.env()
prod <- new.env()

## source the .R files into their respective environments
sys.source("crossvalidate.R", envir = crossvalidate)
sys.source("prod.R", envir = prod)

## show that there are no populates find-able on the search path

> ls()
[1] "crossvalidate" "prod" 
> find("populate")
character(0)

现在,请附加其中一个环境并致电populate()

> attach(crossvalidate)
> populate()
 [1]  1  2  3  4  5  6  7  8  9 10
> detach(crossvalidate)

现在在其他环境中调用该函数

> attach(prod)
> populate()
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
> detach(prod)

显然,每次要使用特定功能时,都需要attach()其环境,然后调用它,然后进行detach()调用。这是一种痛苦。

我的确表示你可以安排在规定的环境中评估R代码(表达式)。例如,您可以eval()使用with()

> with(crossvalidate, populate())
[1]  1  2  3  4  5  6  7  8  9 10

至少现在您只需要一次调用即可运行您选择的populate()版本。但是,如果按其全名调用函数,例如populate.crossvalidate()需要付出太大的努力(根据您的意见),我敢说即使with()这个想法太麻烦了吗?无论如何,当你可以很容易地拥有自己的R包时,为什么要使用它呢。

答案 1 :(得分:14)

不要担心“制作套餐”的复杂性。不要那样想。你要做的是:

  1. 在您正在处理项目的文件夹中,创建一个名为“R”的文件夹
  2. 将您的R代码放在那里,每个文件一个函数
  3. 在项目目录中创建一个描述文件。查看确切格式的现有示例,但您只需要几个字段。
  4. 获取devtools。 install.packages("devtools")
  5. 使用devtools。 library(devtools)
  6. 现在,将您的函数写入R文件夹中的R文件中。要将它们加载到R中,DONT来源它们。做load_all()。您的函数将被加载但不会加载到全局环境中。

    编辑其中一个R文件,然后再次执行load_all()。这将加载R文件夹中的任何已修改文件,从而更新您的功能。

    就是这样。编辑,load_all,冲洗并重复。你已经创建了一个包,但它非常轻巧,你不必处理R的包构建工具的束缚和纪律。

    我已经看过,使用过,甚至编写了代码,试图实现一个轻量级的包装机制来加载对象,没有一个能像devtools那样好。

    所有Hail Hadley!

答案 2 :(得分:3)

您可能需要考虑制作一个包。作为替代方案,您可以查看环境。最后,RStudio的项目可能更接近适合你的项目。