簡體   English   中英

自定義環境中的范圍(功能)

[英]Scoping (functions) in custom environment

我有一個特殊的(虛擬的)function,我想在沙盒環境中使用它:

disable.system.call <- function(...) {
    mc <- match.call()
    if (grepl('system', deparse(mc[[2]])))
        stop('NONO')
    eval(mc, env = .GlobalEnv)        
}

它沒有做任何特別的事情,只是檢查第一個參數的名稱中是否包含system字。 這只是一個 POC 示例。

我稍后做什么:我將這個簡單的函數分配給一些base函數和stats函數,以查看計算的表達式是否確實包含system字作為第一個參數。 例如:

e <- new.env()
eval(parse(text = 'model.frame <- disable.system.call'), envir = e)

這工作起來非常酷,因為沒有system內部的調用就像一個魅力,但過濾器工作:

> eval(parse(text = 'model.frame("1 ~ 1")'), envir = e)
  1
1 1
> eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
Error in model.frame("1 ~ system(\"ls -la\")") : NONO

它甚至使用lm調用,調用model.frame在內部找到一個像字符串這樣的公式:

> eval(parse(text = 'lm(\'1 ~ system("ls -la")\')'), envir = e)
Error in model.frame(formula = "1 ~ system(\"ls -la\")", drop.unused.levels = TRUE) : 
  NONO

我進一步嘗試 go 並將非常簡單的 function ( disable.system.call ) 分配給從as.formula調用的model.frame 不幸的是我沒有得到到目前為止:

> e <- new.env()
> eval(parse(text = 'as.formula <- disable.system.call'), envir = e)
> eval(parse(text = 'as.formula("1 ~ 1")'), envir = e)
1 ~ 1
> eval(parse(text = 'as.formula(\'1 ~ system("ls -la")\')'), envir = e)
Error in as.formula("1 ~ system(\"ls -la\")") : NONO
> eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
  1 system("ls -la")
1 1                0
> eval(parse(text = 'lm(\'1 ~ system("ls -la")\')'), envir = e)

Call:
lm(formula = "1 ~ system(\"ls -la\")")

Coefficients:
     (Intercept)  system("ls -la")  
           1                NA  

據我所知model.frame正在調用as.formula但這不起作用(正如您從上面的輸出中看到的那樣)。 我很確定這不是因為model.frame在自定義環境中調用stats::as.formula就像lmmodel.frame上面調用的那樣。

任何提示和想法都會受到歡迎!

盡管您懷疑情況並非如此,但正在調用stats:::model.frame.default ,而不是環境e中的自定義版本。 (這當然是您通常期望打包函數的行為。在您的第一個示例中看到的奇怪范圍是一種特殊情況,因為lm()使用了“非標准評估”,這在我的答案的底部)。

如下所示,您可以使用trace()查看在每種情況下調用的as.formula()版本:

disable.system.call <- function(...) {
    mc <- match.call()
    if (grepl('system', deparse(mc[[2]])))
        stop('NONO')
    eval(mc, env = .GlobalEnv)        
}
e <- new.env()
eval(parse(text = 'as.formula <- disable.system.call'), envir = e)


# (1) trace custom 'as.formula()' in environment e
trace(e$as.formula)


# Calling model.frame() **does not** call the the custom as.formula()
eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
#   1 system("ls -la")
# 1 1              127

# (2) trace stats:::as.formula()
trace(stats:::as.formula)

# Calling model.frame() **does** call stats:::as.formula()
eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
# trace: as.formula
#   1 system("ls -la")
# 1 1              127

編輯:FWIW,在第一個示例中 lm lm() ) 調用您的自定義model.frame()的原因lm()采用有時稱為“非標准評估”的方法。 (有關該主題的更多信息, 請參閱此 pdf ,這比您可能想要的要多。)關鍵是lm()實際上指示model.frame()在調用環境中進行評估; 在您的情況下,這導致它找到您的 function 版本。

lm()使用非標准評估的原因是model.frame()可以訪問公式中命名的變量,即使它們在調用環境中找到(而不是僅僅能夠訪問通過data參數傳入的變量到lm() )。 正如 Thomas Lumley 在鏈接的 pdf 中所說:

如果要求公式中的變量在數據參數中,生活會簡單得多,但在引入公式時並沒有提出這一要求。

如果您有興趣,這里是lm定義中的相關行:

mf <- match.call(expand.dots = FALSE)
...
mf[[1L]] <- as.name("model.frame")
mf <- eval(mf, parent.frame())

如果您不希望人們能夠使用system ,那么覆蓋定義會更容易。

assignInNamespace(
  "system", 
  function(...) stop("system calls are not allowed"), 
  getNamespace("base")
)

system("pwd")  #throws an error

我在瘋狂猜測您的用例,但您是否允許用戶將任意 R 代碼傳遞給其他應用程序? 在這種情況下,您可能想要編譯您自己的 R 版本,刪除危險函數或用假人替換。


調用 function 時執行自定義代碼的另一種可能性是trace 例如,

trace(system, quote(stop("You have called system")))  #you may also want print = FALSE

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM