简体   繁体   English

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

[英]Overriding a package function inherited by another package

I am trying to override the tidy.source function of the knitr package. 我试图覆盖knitr包的tidy.source函数。 The problem is that tidy.source is defined in the formatR package, which is imported by the knitr package. 问题是tidy.source是在formatR包中定义的,它由knitr包导入。 If I run: 如果我跑:

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

I get the original code. 我得到了原始代码。 So I am tempted to override tidy.source with: 所以我很想用以下方法覆盖tidy.source

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

but I get: 但我得到:

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

In fact tidy.source is defined in formatR and inherited by knitr . 事实上tidy.source被定义formatR和继承knitr With: 附:

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

everything is apparently smooth, but checking again get("tidy.source", envir=asNamespace("knitr")) shows that inside knitr nothing has changed. 一切都显得顺利,但再次检查get("tidy.source", envir=asNamespace("knitr"))表明knitr里面没有任何变化。

Any help? 有帮助吗?

EDIT: 编辑:

This question is partly obsolete due to the new development release of knitr/formatR . 由于knitr / formatR的新开发版本,这个问题已经过时了。 Many thanks to Yihui for noticing this discussion and for deciding to update his package. 非常感谢Yihui注意到这个讨论,并决定更新他的包。 See: 看到:

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

I can definitely switch from Sweave to knitr . 我绝对可以从Sweave切换到knitr

The general question concerning the overriding of an imported package function remains anyway open. 关于覆盖进口包裹功能的一般性问题仍未解决。 Since it is not related to knitr/formatR package anymore, I restate it in more general terms. 由于它与knitr / formatR无关 ,所以我用更一般的术语重述它。

Suppose you have a package main importing the package imp . 假设您有一个包main导入包imp If you load the former, "package:main" shows in the list of the attached packages and both "main" and "sub" show among the names of the loaded namespaces. 如果加载前者, "package:main"显示在附加的包列表中, "main""sub"显示在加载的命名空间的名称中。

Assume that main imports the exported sub function exp.sub.func , which calls in turns the non-exported sub function prv.sub.func . 假设main导入导出的子函数 exp.sub.func ,该函数调用非导出的子函数 prv.sub.func If you want to change/customise exp.sub.func with your exp.sub.func.mod , you may think of using: 如果你想改变/自定义exp.sub.funcexp.sub.func.mod,你可能会想到使用:

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

As a result, by running sub::exp.sub.func you will get your patched version (that is exp.sub.func.mod ). 因此,通过运行sub::exp.sub.func您将获得修补版本(即exp.sub.func.mod )。
Unfortunately, as far as your exp.sub.func.mod keeps on relying on prv.sub.func , you get the error: 不幸的是,只要您的exp.sub.func.mod继续依赖prv.sub.func ,您就会收到错误:

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

In fact: 事实上:

environment(sub::exp.sub.func) 

returns now: <environment: R_GlobalEnv> while it was <environment: namespace:sub> before patching. 现在返回: <environment: R_GlobalEnv>虽然在修补之前是<environment: namespace:sub>

The question is: how to move the patched function to the proper namespace ? 问题是: 如何将修补的函数移动到正确的命名空间

To implement the problem above you can use whatever packages of course; 要实现上述问题,您当然可以使用任何包; in my case I used knitr and formatR as main and imported namespace and tidy.source() as patched function. 在我的例子中,我使用knitrformatR作为主要和导入的命名空间,并使用tidy.source()作为修补函数。

Changing the function in the formatR namespace doesn't change what knitr uses because knitr is already loaded. 更改formatR命名空间中的函数不会更改knitr使用的内容,因为已经加载了knitr So, you could unload and reload it. 所以,你可以卸载并重新加载它。

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

If all you want is comments after function arguments, I have added the support in the development version , and you can install it from Github . 如果你想要的只是函数参数后面的注释,我在开发版本中 添加了支持,你可以从Github安装它

Normally it is a bad idea to modify packages using assignInNamespace() , as its documentation shows. 通常,如文档所示,使用assignInNamespace()修改包是个坏主意。

I am perhaps close to a solution but I have to deal with non-exported formatR functions. 我可能接近解决方案,但我必须处理非导出的formatR函数。 In fact the original tidy.source code, and therefore the patched version, has calls to non-exported package functions, eg reflow_comments . 实际上,原始的tidy.source代码以及修补版本都调用了非导出的包函数,例如reflow_comments

To illustrate the problem and the step I followed, let's start with a test patched tidy.source calling a private formatR function. 为了说明问题和我遵循的步骤,让我们从测试修补的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 parameters are necessary as passed by knit used ahead. source, output, text参数是必要的,因为前面使用的knit传递。

Now I can patch the tidy.source with tidy.source.mod : 现在我可以用打补丁的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")) 

We can check the results: 我们可以查看结果:

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

Apparently tidy.source was correctly substituted with tidy.source.mod ; 显然tidy.source被正确地与tidy.source.mod取代; namespaces were updated so tidy.source has access to (the first line of) non-exported reflow_comments function. 命名空间已更新,因此tidy.source可以访问(第一行)非导出的reflow_comments函数。

To deal with knitr we need also a file to knit, here I use a text string for simplicity. 为了处理knitr,我们还需要一个文件来编织,这里我使用文本字符串来简化。

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

library("knitr") 

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

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

\\end{document}  
"                

Is knitr able to see the patched tidy.source ? knitr能看到修补的tidy.source吗? We can check this with the help of R debug . 我们可以在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)                                 

Unfortunately from knit the patched tidy.source is visible, but it can't access non-exported formatR functions, which for knit is normally possible via the non-patched tidy.source . 不幸的是,从编织修补的tidy.source是可见的,但它无法访问非导出的formatR函数,对于knit通常可以通过非修补的tidy.source

Here some hints may be that formatR::tidy.source() doesn't work either: 这里有一些提示可能是formatR::tidy.source()也不起作用:

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

The environment of namespace:formatR is: 命名空间的环境:formatR是:

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

That was <environment: namespace:formatR> before patching (see info in Listing 2). 在修补之前,这是<environment: namespace:formatR> (参见清单2中的信息)。 While environment(tidy.source ) was easily reset when patching, for namespace:formatR we get an error: 虽然在修补时很容易重置environment(tidy.source ) ,但对于namespace:formatR我们得到一个错误:

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

I am still searching.... 我还在寻找....

This is a possible buggy attempt to allow inline comments after function arguments by tidy.source function included in the formatR package. 这是一种可能的错误尝试,允许通过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) 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 R包开发:使用另一个包中的函数覆盖一个包的函数? - R Package development: overriding a function from one package with a function from another? 将 package 和 function 称为 arguments 在另一个 ZC1C425268E68389414AB5074C17 - Referring to package and function as arguments in another function 在另一个程序包或函数中装载一个程序包时,是否有可能得到通知? - Is it possible to get notified when a package is loaded within another package or function? 使用另一个R包函数而不使用整个包作为依赖项 - Using another R package function without using the whole package as dependency 覆盖VennDiagram包的总和值 - Overriding the sum values for the VennDiagram package 可以在另一个文件(或程序包)中定义垂直函数吗? - Can a plumbed function be defined in another file (or package?) 是否有可能从另一个包中的函数@inheritParams? - Is it possible to @inheritParams from a function within another package? 使用包依赖项覆盖R中的base :: switch - Overriding base::switch in R with package dependencies 未声明函数(函数作为另一个函数的参数)-创建包时的XPtr - function was not declared (function as argument to another)- XPtr when creating package 如何通过命名空间查看哪个R包或函数已经加载了另一个R包 - How to see which R package or function has loaded another R package via namespace
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM