4

简而言之(实际问题)

如何访问定义 S4 引用类的实际源代码/表达式?setRefClass(请参阅)或返回的对象(因此获取它之后,而不是通过调查实际源文件)?getClass("MyClass")getRefClass("MyClass")

我做的功课

由于一切都是 R 中的对象,我可以检索源代码/表达式

1)通过简单地调查相应对象的常规功能:

foo <- function(x) print(x)

> foo
function(x) print(x)

2)通过获取特定方法的方法对象的形式方法selectMethod

setGeneric(name="myMethod", signature=c("x"),
    def=function(x) standardGeneric("myMethod")       
)
setMethod(
    f="myMethod", 
    signature=signature(x="numeric"), 
    definition=function(x) print(x)
)
def <- selectMethod(f="myMethod", signature=c(x="numeric"))

# Get actual source code/expression
> attributes(def)$srcref
function(x) print(x)

但是 S4 参考类的情况似乎有所不同:

setRefClass(Class="MyClass", fields=list(x.1="character"))

def <- getRefClass("MyClass")

# Inspect object >> no expression
> def
Generator object for class "MyClass":

Class fields:

Name:        x.1
Class: character

 Class Methods:  
    "callSuper", "copy", "export", "field", "getClass", "getRefClass", "import", 
"initFields", "show", "trace", "untrace"


 Reference Superclasses:  
    "envRefClass"

def.temp <- attributes(attributes(def)$.xData$def)

# Inspect attributes >> no expression
> attributes(def.temp)
$names
 [1] "fieldClasses"    "fieldPrototypes" "refMethods"      "refSuperClasses"
 [5] "slots"           "contains"        "virtual"         "prototype"      
 [9] "validity"        "access"          "className"       "package"        
[13] "subclasses"      "versionKey"      "sealed"          "class"          

# Alternatively:
> names(attributes(getClass("MyClass")))
 [1] "fieldClasses"    "fieldPrototypes" "refMethods"      "refSuperClasses"
 [5] "slots"           "contains"        "virtual"         "prototype"      
 [9] "validity"        "access"          "className"       "package"        
[13] "subclasses"      "versionKey"      "sealed"          "class"  

我似乎找不到存储准确定义类的实际源代码/表达式的属性。

只是为了确保:这个表达式是我想要访问的

setRefClass(Class="MyClass", fields=list(x.1="character"))

背景/动机

我经常使用 S4 引用类 ( ),因此像类继承?setRefClass这样的OOP 方面在我的日常工作中扮演着重要角色。我还遵循“每个文件一个定义”的范例,以使事情井井有条,因此各种类定义存储在单独的文件中,其中文件名对应于各个类的名称。

与生活中的一切一样,这种方法有一些优点,但也有一些固有的缺点:

看点一

无论长短,您最终都会得到一个与各个源文件的字母文件顺序不再匹配的继承结构。因此,简单地一个接一个地获取一个文件将导致在某个特定点出现错误,其中一些所需的超类尚未被获取。

dir.create("classes", showWarnings=FALSE)
write("setRefClass(Class=\"A\", contains=\"B\", fields=list(x.3=\"logical\"))", 
    file="classes/class_A.R")
write("setRefClass(Class=\"B\", contains=\"C\", fields=list(x.2=\"numeric\"))", 
    file="classes/class_B.R")
write("setRefClass(Class=\"C\", fields=list(x.1=\"character\"))", 
    file="classes/class_C.R")

class_A.R是 folder 中的第一个文件classes,但为了获取它,我们首先需要获取源class_B.R(因为该文件定义了 class B),这反过来又需要 class C,因此需要先获取class_C.R.

因此,正确的排序规则是:

c("class_C.R", "class_B.R", "class_A.R")

方面2

对于某些任务,您确实想要/需要“每个文件多个定义”范例:在并行化时快速轻松地将必要的对象/函数/类分配给工作进程,在实际构建包时组织代码等。

path <- "classes/classes.R"
file.create(path)
write("setRefClass(Class=\"C\", fields=list(x.1=\"character\"))", 
    file=path, append=TRUE)
write("setRefClass(Class=\"B\", contains=\"C\", fields=list(x.2=\"numeric\"))", 
    file=path, append=TRUE)
write("setRefClass(Class=\"A\", contains=\"B\", fields=list(x.3=\"logical\"))", 
    file=path, append=TRUE)

广告方面 1

我不喜欢保留某种手动整理参考来指定正确的采购顺序的想法,因为我认为这是计算机可以轻松为我完成的工作(找出正确的整理)。您在这里唯一需要做的就是找出每个类的超类(某种依赖关系),然后检索正确的排序规则就是小菜一碟。

编辑

如果有人感兴趣:我确实为此提出了一种工作方法。如果您想查看一些代码,请给我留言。它基于解析(不评估)相应的类 def 源文件,以调查contains列出超类的参数的值。然后对这些超类的源文件递归地重复整个过程,直到最终得到正确的排序规则。也不是那么费时间。

这是大纲:

x <- list.files("classes", full.names=TRUE)    
code <- base::parse(file=x[1])

> code 
expression(setRefClass(Class="A", contains="B", fields=list(x.3="logical")))

superclasses <- eval(code[[1]][["contains"]])
> superclasses
[1] "B"

# Next: investigate source file for class 'B'

广告方面 2

我也不喜欢手动复制和粘贴,所以我实现了一个例程,允许我将存储在单个文件中或从各个对象中提取的源代码合并到单个“合并”文件(通过deparse(<source_code_expression>)write(..., append=TRUE))。至于类,正确的排序规则在这里也很重要,否则当您尝试获取合并文件时会再次出现错误。

对于这两个方面,能够选择如何获取类/函数/方法的实际源代码/表达式会很好:

  1. 或者基于调查存储在相应源文件中的代码 ( parse(file=*))
  2. 或基于直接从相应对象访问所需信息。

第二个选项将是上述实际问题的链接。

4

2 回答 2

1

该命令的“源”代码未存储,因此您不会通过检查对象看到它。

setRefClass通过在控制台输入并点击 来查看源代码[ENTER]。请注意,您所做的只是将参数传递给函数......没有定义新的表达式。因此,当您getRefClass获得班级所知道的所有信息时。

您可以通过创建一个 parseRefClassArgs 函数来重新构建它,该函数重新构建 setRefClass 的参数。

于 2012-08-15T18:38:40.927 回答
1

类定义

由于我们无法评估文件(因为我们不知道排序顺序),source或者探索定义的类不在桌面上。在这里,我们将每个文件中的文本解析为一个字符向量,保留以“setRefClass”开头的解析行。解析器去除空白并进行其他转换以使文本格式更统一,尽管以下将依赖于一致的类定义(例如,使用命名参数)。

fls <- dir()
names(fls) <- fls
p0 <- lapply(fls, function(fl) as.character(parse(text=readLines(fl))))
p1 <- lapply(p0, grep, pattern="^setRefClass", value=TRUE)

让我们瞄准一个包含正在定义的类、包含的类和类定义所在的文件名的 data.frame

df <- data.frame(Class=NA_character_, contains=NA_character_,
                 File=rep(fls, sapply(p1, length)),
                 stringsAsFactors=FALSE)

然后使用模式匹配/替换填充它

p2 <- unlist(p1, use.names=FALSE)
pat <- '.*Class = "([[:alnum:]]+)".*'
df[,"Class"] <- sub(pat, "\\1", p2)

pat <- '.*contains = "([[:alnum:]]+)".*'
idx <- grep(pat, p2)
df[idx,"contains"] <- sub(pat, "\\1", p2[idx])

以(为了好玩,我在 class_A.R 文件中添加了一个同样从 B 派生的 A1 类)

> df
  Class contains      File
1     A        B class_A.R
2    A1        B class_A.R
3     B        C class_B.R
4     C     <NA> class_C.R

收获 Class / contains 数据帧的另一种策略是拦截对 setRefClass 的调用

gClass <- character()
gcontains <- character()
setRefClass <- function(Class, fields, contains=NA_character_, ...)
{
    gClass <<- c(gClass, Class)
    gcontains <<- c(gcontains, contains)
}

gClass并且gcontains将包含在获取相关文件后用于构建依赖关系图的相关数据(假设可以在没有类定义可用的情况下获取文件)。

for (fl in dir()) source(fl)

依赖项

我们想要的是依赖关系图,用于具有依赖关系的类。所以我们将使用来自Bioconductor的graphRBGL包来构建一个合适的图

## From --> To == contains --> Class
m <- as.matrix(df[!is.na(df$contains), c("contains", "Class")])
gr <- ftM2graphNEL(m, edgemode="directed")

然后要求从我们的每个基本包 ( df[is.na(df$contains), "Class"]) 开始进行广度优先搜索,并使用结果顺序来检索适当的排序规则

o <- bfs(gr, "C")                       # order: breadth-first search
unique(df[match(o, df$Class), "File"])

所以

classDefFilesCollateOrder <- function(fls)
{
    names(fls) <- fls
    p0 <- lapply(fls, function(fl) as.character(parse(text=readLines(fl))))
    p1 <- lapply(p0, grep, pattern="^setRefClass", value=TRUE)

    df <- data.frame(Class=NA_character_, contains=NA_character_,
                     File=rep(fls, sapply(p1, length)),
                     stringsAsFactors=FALSE)

    p2 <- unlist(p1, use.names=FALSE)
    pat <- '.*Class = "([[:alnum:]]+)".*'
    df[,"Class"] <- sub(pat, "\\1", p2)

    pat <- '.*contains = "([[:alnum:]]+)".*'
    idx <- grep(pat, p2)
    df[idx, "contains"] <- sub(pat, "\\1", p2[idx])

    ## From --> To == contains --> Class
    m <- as.matrix(df[!is.na(df$contains), c("contains", "Class")])
    gr <- ftM2graphNEL(m, edgemode="directed")

    ## single class only
    base <- df$Class[is.na(df$contains)]
    if (length(base) != 1)
        stop("don't yet know how to deal with more than 1 base class")
    o <- bfs(gr, base)

    unique(df[match(o, df$Class), "File"])
}
于 2012-08-15T21:11:11.273 回答