首页 > 解决方案 > 是否可以通过不同的包“反转”功能屏蔽?

问题描述

我维护一个包,供我工作的公司使用。并不是所有的程序员都像他们应该做的那样勤奋,而且他们经常library()为他们需要的每个包发送垃圾电话。这通常会导致一个包中的函数被稍后加载的另一个包屏蔽,从而导致代码中其他地方的复杂性和奇怪的错误。然后通过使用package::function语法来解决这些错误,当它用于一个包函数时看起来很奇怪,但它周围没有其他任何东西,即使来自同一包中的其他函数(未屏蔽)也是如此。

显然,在这种情况下,正确的做法是不要使用 加载每个单独的包library(),将其保存为代码中最常用的包,然后使用package::function不常用的包的语法。

然而,这超出了我的控制范围。所以我试图通过向我们的内部包添加一个处理包“优先级”的函数来帮助程序员,让他们定义在调用给定函数名时哪个包应该优先。

以下代码有效:

setDefaultPackage <- function(pkg, functions = NULL) {
    pkg = paste0("package:", pkg)
    
    if (is.null(functions)) functions <- utils::lsf.str(pkg)
    
    for(f in functions) {
        # only reassign if name doesn't yet exist or if associated environment is
        # NOT the global environment.
        
        if (exists(f)) {
            canUnmask <- tryCatch({
                getNamespaceName(environment(get(f, pos = parent.frame())))
                TRUE
            }, error = function (e) {
                FALSE
            })
        } else {
            canUnmask <- TRUE
        }
        
        if (canUnmask) {
            x <- tryCatch({
                get(pos = pkg, f)
            }, error = function(e) {
                stop("Package ", pkg, "does not have a function called ", f)
            })
            
            assign(f, x, pos = parent.frame())
        }
    }
}

library(stats)
environmentName(environment(filter))
#> [1] "stats"

# now mask stats::filter with dplyr::filter
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
environmentName(environment(filter))
#> [1] "dplyr"

# restore stats::filter to the top
setDefaultPackage("stats")
environmentName(environment(filter))
#> [1] "stats"

# restore dplyr::filter to the top
setDefaultPackage("dplyr")
environmentName(environment(filter))
#> [1] "dplyr"

# Fails to unmask packaged names masked by local names
filter <- function() {print(1)}
environmentName(environment(filter))
#> [1] "R_GlobalEnv"

setDefaultPackage("stats")
environmentName(environment(filter))
#> [1] "R_GlobalEnv" -- (unchanged!) --

reprex 包于 2021-08-11 创建 (v2.0.0 )

用户可以定义他们是否要filter调用stats::filterdplyr::filter

然而,这个函数非常不优雅:它通过在本地框架中定义函数名称(在上面的示例中,全局框架;如果在另一个函数中调用,调用函数的框架)来工作,淹没该框架的名称空间。

在此处输入图像描述

在这种情况下,命名空间中充斥着来自stats和的对象名称dplyr,因为我已经调用setDefaultPackage了两者(冲突的名称当前指向,stats因为那是我最后一次调用)。

一种更简洁的方法是简单地修改搜索路径:

search()
#> [1] ".GlobalEnv"        "package:dplyr"     "tools:rstudio"     "package:stats"     "package:graphics"  "package:grDevices" "package:utils"     "package:datasets" 
#> [9] "package:methods"   "Autoloads"         "package:base"  

由于我加载dplyrstats,它在搜索列表中排在第一位。

如果我可以简单地修改该列表,改组package:dplyrpackage:stats左右,那就太棒了。那可能吗?

也就是说,有没有办法将特定的包放在搜索列表的顶部?

someMagicalFunction("stats")
search()
#> [1] ".GlobalEnv"      "package:stats"     "package:dplyr"     "tools:rstudio"     "package:graphics"  "package:grDevices" "package:utils"     "package:datasets" 
#> [9] "package:methods" "Autoloads"         "package:base"

标签: rpackager-package

解决方案


您可以使用以下自定义功能:

setDefaultPackage <- function(pkg, functions = NULL){
  pkg1 <- paste0("package:", pkg)
  nms <- paste(pkg, 'functions', sep = '_')
  if (is.null(functions))  {
    if (any(search() == pkg1)) 
      detach(pkg1, character.only = TRUE)
    library(pkg, character.only = TRUE)
  }
  else {
    if (any(search() == nms)) 
      detach(nms, character.only = TRUE)
    env <- list2env(mget(functions, as.environment(pkg1)))
    attach(env, name = nms)
  }
}

例子:

> library(dplyr)
> search()
 #> [1] ".GlobalEnv"        "package:dplyr"     "tools:rstudio"     "package:stats"    
 #> [5] "package:graphics"  "package:grDevices" "package:utils"     "package:datasets" 
 #> [9] "package:methods"   "Autoloads"         "package:base"   

现在,如果您想优先考虑stats包中的所有功能:

> setDefaultPackage('stats')

#> Attaching package: ‘stats’

#> The following objects are masked from ‘package:dplyr’:

#>    filter, lag

> search()
#> [1] ".GlobalEnv"        "package:stats"     "package:dplyr"     "tools:rstudio"    
#> [5] "package:graphics"  "package:grDevices" "package:utils"     "package:datasets" 
#> [9] "package:methods"   "Autoloads"         "package:base"   

我们看到statspackage 出现在dplyrpackage 之前,意味着我们可以使用lagfilterfrom statspackage。

让我们将其还原为dplry之前的内容stats

> setDefaultPackage('dplyr')
> search()
 #> [1] ".GlobalEnv"        "package:dplyr"     "tools:rstudio"     "package:stats"    
 #> [5] "package:graphics"  "package:grDevices" "package:utils"     "package:datasets" 
 #> [9] "package:methods"   "Autoloads"         "package:base"   

如果从一开始,我们想使用lagfromdplyrfilterfrom怎么办stats?即在搜索路径中,dplyr 出现在 stats 之前,就像以前一样。然后你可以运行

setDefaultPackage('stats', 'filter')

现在filter要使用的是 from stats,whilelag是 from dplyr


推荐阅读