【问题标题】:Overriding a package function inherited by another package覆盖另一个包继承的包函数
【发布时间】:2012-11-15 17:14:07
【问题描述】:

我正在尝试覆盖 knitr 包的 tidy.source 函数。问题是tidy.source 是在formatR 包中定义的,它是由knitr 包导入的。 如果我跑:

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

我得到了原始代码。 所以我很想用:

覆盖tidy.source

assignInNamespace ("tidy.source", function()print("My tidy.source"), "knitr"),

但我明白了:

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

实际上tidy.source 是在formatR 中定义的,并被knitr 继承。与:

assignInNamespace ("tidy.source", function()print("My tidy.source"), "formatR")

一切看起来都很顺利,但再次检查get("tidy.source", envir=asNamespace("knitr")) 显示knitr 内部没有任何变化。

有什么帮助吗?

编辑:

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

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

我绝对可以从 Sweave 切换到 knitr

关于覆盖导入包函数的一般问题仍然悬而未决。由于它不再与 knitr/formatR 包相关,我用更笼统的术语重申它。

假设你有一个包main 导入包imp。如果加载前者,"package:main" 会显示在附加包的列表中,"main""sub" 都会显示在加载的命名空间的名称中。

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

assign("exp.sub.func", exp.sub.func.mod, asNamespace ("sub"))

因此,通过运行sub::exp.sub.func,您将获得您的补丁版本(即exp.sub.func.mod)。
不幸的是,只要你的 exp.sub.func.mod 继续依赖 prv.sub.func,你就会得到错误:

Error in [...] : object 'prv.sub.func' not found

事实上:

environment(sub::exp.sub.func) 

现在返回:<environment: R_GlobalEnv>,而在修补之前它是<environment: namespace:sub>

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

要解决上述问题,当然可以使用任何软件包;在我的例子中,我使用 knitrformatR 作为主要和导入的命名空间,并使用 tidy.source() 作为修补函数。

【问题讨论】:

  • 你为什么要覆盖tidy.source
  • 我最近需要做一些类似的事情,并将其格式化如下:assignInNamespace("grid.curve", grid.curve, ns = "grid")。当我运行这个命令时,我修改后的grid.curve 已经被引入工作区。这与您使用的规范略有不同,但在我看来,您的规范也应该可以工作。但是,我不是在继承的环境中工作。
  • @mnel 这是来自 Google 的 cmets 指南: > 短 cmets 可以放在代码之后,前面有两个空格,#,然后是一个空格。 google-styleguide.googlecode.com/svn/trunk/… 这也是很常见的注释做法,但是参数后的内联 cmets 不适用于“formatR”。
  • @BryanHanson 我确实喜欢你。 `function()print("My tidy.source"),' 仅用于使代码自包含。
  • 可能值得在 formatRknitr 页面上提出有关更改 tidying 函数或格式样式的问题。

标签: r namespaces packages knitr


【解决方案1】:

如果你想要的只是函数参数后的 cmets,我在 development version 中有 added 的支持,你可以 install it from Github

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

【讨论】:

  • 仅供参考,新版本现已在 CRAN 上。
【解决方案2】:

formatR 包中包含的 tidy.source 函数在函数参数之后允许内联 cmets 可能存在错误。

## ============ 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) 

【讨论】:

    【解决方案3】:

    我可能接近解决方案,但我必须处理非导出的 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 函数,这对于 knit 通常可以通过未打补丁的 tidy.source

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

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

    namespace:formatR的环境是:

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

    那是修补之前的&lt;environment: namespace:formatR&gt;(参见清单 2 中的信息)。虽然environment(tidy.source ) 在修补时很容易重置,但对于 namespace:formatR 我们得到一个错误:

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

    我还在寻找....

    【讨论】:

    • Antonio - 你有没有找到解决方案?只是我正在尝试用另一个包做类似的事情......
    • @TomWenseleers:我非常接近,但是,在作者一辉的补丁之后,我停止了调查。也许现在有了 R 3.0,事情变得更容易了。
    【解决方案4】:

    更改 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")
    

    【讨论】:

    • 经过反复试验,我能够获得 knitrformatR 共享的已修补的 tidy.source,但我在处理非导出的 formatR 函数时仍然遇到困难。我正在添加一个答案来解释我的步骤。
    • 非常感谢@GSee!!!你救了我!!在 R 中这是必要的,这太糟糕了!这是一个陷阱,如此反直觉!我可能会调试几个小时...非常感谢!
    猜你喜欢
    • 2020-01-10
    • 2014-03-22
    • 2021-11-30
    • 1970-01-01
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-01
    相关资源
    最近更新 更多