覆盖另一个包继承的包函数

时间:2012-11-27 23:32:20

标签: r namespaces packages knitr

我正在尝试覆盖tidy.source包的knitr功能。问题是tidy.source包中定义了formatRknitr包由get("tidy.source", envir=asNamespace("knitr"))包导入。 如果我跑:

tidy.source

我得到原始代码。 所以我很想用{/ p>覆盖assignInNamespace ("tidy.source", function()print("My tidy.source"), "knitr")

Error in bindingIsLocked(x, ns) : no binding for "tidy.source"

但我明白了:

tidy.source

事实上,formatRknitr中定义,并由assignInNamespace ("tidy.source", function()print("My tidy.source"), "formatR")继承。用:

get("tidy.source", envir=asNamespace("knitr"))

一切都很顺利,但再次检查knitr表明内部"package:main"没有任何变化。

有任何帮助吗?

编辑:

由于 knitr / formatR 的新开发版本,此问题已部分过时。非常感谢Yihui注意到这个讨论,并决定更新他的包。参见:

https://github.com/yihui/formatR/commit/6f70360f359caa8d2bb33190a1c89530defb0e98

我绝对可以从 Sweave 切换到 knitr

关于覆盖进口包裹功能的一般性问题仍未解决。由于它不再与 knitr / formatR 包相关,我将以更一般的术语重述它。

假设您有一个包 main 导入包 imp 。如果您加载前者,"main"显示在附加的包列表中,"sub"assign("exp.sub.func", exp.sub.func.mod, asNamespace ("sub")) 显示加载的命名空间的名称。

假设 main 导入导出的 sub 函数 exp.sub.func ,该函数调用未导出的 function prv.sub.func 。如果您想使用 exp.sub.func.mod 更改/自定义 exp.sub.func ,您可以考虑使用:

sub::exp.sub.func

因此,通过运行Error in [...] : object 'prv.sub.func' not found ,您将获得修补后的版本(即 exp.sub.func.mod )。
不幸的是,只要您的 exp.sub.func.mod 继续依赖于 prv.sub.func ,就会出现错误:

environment(sub::exp.sub.func) 

事实上:

<environment: R_GlobalEnv>

现在返回:<environment: namespace:sub>,而在修补之前它是{{1}}。

问题是:如何将修补的函数移动到正确的命名空间

要实现上述问题,您当然可以使用任何包;在我的例子中,我使用 knitr formatR 作为主要和导入的命名空间,并使用 tidy.source()作为修补功能。

4 个答案:

答案 0 :(得分:2)

更改 formatR 命名空间中的功能不会更改 knitr 使用的功能,因为 knitr 已加载。所以,你可以卸载并重新加载它。

assignInNamespace("tidy.source", function()print("My tidy.source"), "formatR")
detach('package:knitr', unload=TRUE)
library(knitr)
get("tidy.source", envir=asNamespace("knitr"))
#function()print("My tidy.source")

答案 1 :(得分:2)

如果你想要的只是功能参数后面的评论,我在added中有development version支持,你可以install it from Github

正如文档所示,通常使用assignInNamespace()修改包是个坏主意。

答案 2 :(得分:0)

我可能关闭解决方案,但我必须处理未导出的 formatR 函数。实际上,原始的 tidy.source 代码以及修补版本都调用了非导出的包函数,例如: reflow_comments

为了说明问题和我遵循的步骤,让我们从测试修补 tidy.source 开始调用私有 formatR 函数。

### Listing 1 - Modified tidy.source             

tidy.source.mod=function (source, output, text){ 
  #Print body first line of reflow_comments      
  head(reflow_comments,1)                        
}                                                

source, output, text参数是必要的,因为前面使用了 knit

现在我可以使用 tidy.source.mod 修补 tidy.source

### Listing 2 - Patch tidy.source                       

## General clean up                                     
if("knitr" %in% loadedNamespaces() ) detach('package:knitr', unload=TRUE)
if("formatR" %in% loadedNamespaces() ) detach('package:formatR', unload=TRUE)
if(exists("tidy.source"))rm(tidy.source)                
library("formatR")                                      

## Info                                                 
environment(tidy.source )                               
# <environment: namespace:formatR>                      
environment(formatR::tidy.source )                      
# <environment: namespace:formatR>                      

## Change tidy.source with tidy.source.mod              
unlockBinding("tidy.source", env=as.environment("package:formatR"))
assign("tidy.source", tidy.source.mod, envir=as.environment("package:formatR"))
lockBinding("tidy.source", env=as.environment("package:formatR"))
unlockBinding("tidy.source", env=asNamespace ("formatR"))
assign("tidy.source", tidy.source.mod, asNamespace ("formatR") )
environment(tidy.source)= asNamespace( "formatR" )      
lockBinding("tidy.source", env=asNamespace ("formatR")) 

我们可以查看结果:

### Listing 3 - Check results                                                  

getAnywhere(tidy.source)                                                       
# A single object matching 'tidy.source' was found                             
# It was found in the following places                                         
#   .GlobalEnv                                                                 
#   package:formatR                                                            
#   namespace:formatR                                                          
# with value                                                                   

# function (){                                                                 
#   head(reflow_comments,1)                                                    
# }                                                                            
# <environment: namespace:formatR>                                             

tidy.source()                                                                  
# 1 function (text, idx = grepl("^\\\\s*#+", text), width = getOption("width"))

显然 tidy.source tidy.source.mod 正确替换;命名空间已更新,因此 tidy.source 可以访问(第一行)非导出的reflow_comments函数。

要处理 knitr ,我们还需要一个要编织的文件,为了简单起见,我在这里使用文本字符串。

### Listing 4 - Sample file/text to knit

library("knitr") 

text="           
\\documentclass{article}
\\begin{document}

<<comme, include=TRUE>>=
print('hello')   
@                

\\end{document}  
"                

knitr 是否能够看到已修补的 tidy.source ?我们可以在R debug 的帮助下进行检查。

debug(knit)                                   
knit(text=text) #will enter debug session (prompt s'd be like 'Browse[2]>')
# debugging in: knit(text = text)             
# debug: {                                    
# knit body, very long, omitted               
# }                                           
tidy.source #command given inside the debug session
# function (){                                
#   head(reflow_comments,1)                   
# }                                           
tidy.source() # :-( reflow_comments is not accessible
Q  #quit debug session                        

undebug(knit)                                 

不幸的是,从 knit 可以看到已修补的 tidy.source ,但它无法访问未导出的 formatR 函数,这对于< em> knit 通常可以通过非修补的 tidy.source 来实现。

这里的一些提示可能是formatR::tidy.source()也不起作用:

formatR::tidy.source()
# Error in head(reflow_comments, 1) (from #2) : object 'reflow_comments' not found

命名空间:formatR 的环境是:

environment(formatR::tidy.source )
# <environment: R_GlobalEnv>

在修补之前是<environment: namespace:formatR>(参见清单2中的信息)。虽然在修补时很容易重置environment(tidy.source ),但对于 namespace:formatR ,我们收到错误:

environment(formatR::tidy.source )=asNamespace( "formatR" )
# Error in environment(formatR::tidy.source) = asNamespace("formatR") :
#  object 'formatR' not found 

我还在寻找......

答案 3 :(得分:0)

这是一种可能的错误尝试,允许在 formatR 包中包含的tidy.source函数的函数参数之后进行内联注释。

## ============ Possible (wrong?) ideas on inline comments ============

  tidy.source.mod=  function (source = "clipboard", keep.comment = getOption("keep.comment",
    TRUE), keep.blank.line = getOption("keep.blank.line", TRUE),
    keep.space = getOption("keep.space", FALSE), replace.assign = getOption("replace.assign",
        FALSE), left.brace.newline = getOption("left.brace.newline",
        FALSE), reindent.spaces = getOption("reindent.spaces",
        4), output = TRUE, text = NULL, width.cutoff = getOption("width"),
    ...)
{     
    if (is.null(text)) {
        if (source == "clipboard" && Sys.info()["sysname"] ==
            "Darwin") {
            source = pipe("pbpaste")
        }
        text = readLines(source, warn = FALSE)
    } 
    if (length(text) == 0L || all(grepl("^\\s*$", text))) {
        if (output)
            cat("\n", ...)
        return(list(text.tidy = "", text.mask = ""))
    } 
    text.lines = text
    if (keep.comment) {
        if (!keep.space)
            text.lines = gsub("^[[:space:]]+|[[:space:]]+$",
                "", text.lines)
        head.comment = grepl("^[[:space:]]*#", text.lines)
        if (any(head.comment)) {
            text.lines[head.comment] = gsub("\"", "'", text.lines[head.comment])
        }
        if (!keep.space) {
            head.comment = head.comment & !grepl("^\\s*#+'",
                text.lines)
            text.lines = reflow_comments(text.lines, head.comment,
                width.cutoff)
            head.comment = grepl("^[[:space:]]*#", text.lines)
        }
        text.lines[head.comment] = sprintf("invisible(\"%s%s%s\")",
            begin.comment, text.lines[head.comment], end.comment)
        blank.line = grepl("^[[:space:]]*$", text.lines)
        if (any(blank.line) && keep.blank.line) {
            else.line = grep("^[[:space:]]*else(\\W|)", text.lines)
            for (i in else.line) {
                j = i - 1
                while (blank.line[j]) {
                  blank.line[j] = FALSE
                  j = j - 1
                  warning("removed blank line ", j, " (you should not put an 'else' in a separate line!)")
                }
            }
            text.lines[blank.line] = sprintf("invisible(\"%s%s\")",
                begin.comment, end.comment)
        }
        text.lines = mask_inline(text.lines)
    } 
    #modified code
    ic=grepl( "%InLiNe_IdEnTiFiEr%", text.lines)
    text.lines[ic]=substr(text.lines[ic], 1, nchar(text.lines[ic])-1)
    text.lines[ic]=  paste0(text.lines[ic], "%InLiNe_IdEnTiFiEr_mod%\"")
    #end modified code
    text.mask = tidy_block(text.lines, width.cutoff, replace.assign)
    text.tidy = if (keep.comment)
        unmask.source(text.mask)
    else text.mask
    text.tidy = reindent_lines(text.tidy, reindent.spaces)
    if (left.brace.newline)
        text.tidy = move_leftbrace(text.tidy, reindent.spaces)
    #modified code
    text.tidy= unlist(sapply(text.tidy, strsplit, "%InLiNe_IdEnTiFiEr_mod%", USE.NAMES=FALSE))
    #end modified code
    if (output)
        cat(paste(text.tidy, collapse = "\n"), "\n", ...)
    invisible(list(text.tidy = text.tidy, text.mask = text.mask))
}     
## ====================================================


## ============ Implementation ============

## Clean-up
if("formatR" %in% loadedNamespaces() ) detach('package:formatR', unload=TRUE)
if(exists("tidy.source"))rm(tidy.source)
library("formatR")


## String with inline comments after arguments
text.input="paste(1 # comm
   ,7)
"     
## The same in vector format
text.input=strsplit(text.input, "\n")[[1]]

## Implementation without patch
tidy.source(text=text.input) #newline removed with  wrong result!
# paste(1  # comm, 7) 


# Tentative patch
unlockBinding("tidy.source", as.environment("package:formatR") )
assign("tidy.source", tidy.source.mod, pos="package:formatR")
environment(tidy.source)= asNamespace( "formatR" )

## Implementation with patch
tidy.source(text=text.input) # apparently ok:
# paste(1  # comm
# , 7)