簡體   English   中英

R通用調度到附加環境

[英]R generic dispatching to attached environment

我有很多功能,我試圖通過在環境中定義它們並附加環境來保持我的工作區干凈。 一些函數是 S3 泛型,它們似乎不能很好地使用這種方法。

我遇到的一個最小示例需要 4 個文件:

測試樂趣

ttt.xxx <- function(object) print("x")

ttt <- function(object) UseMethod("ttt")

ttt2 <- function() {
  yyy <- structure(1, class="xxx")
  ttt(yyy)
}

在 testfun.RI 中定義了一個 S3 泛型 ttt 和一個方法 ttt.xxx,我還定義了一個調用泛型的函數 ttt2。

測試環境

test_env <- new.env(parent=globalenv()) 
source("testfun.R", local=test_env) 
attach(test_env)

在 testenv.RI 源 testfun.R 到我附加的環境中。

測試1.R

source("testfun.R")
ttt2()
xxx <- structure(1, class="xxx")
ttt(xxx)

test1.R 將 testfun.R 提供給全局環境。 ttt2 和直接函數調用都有效。

測試2.R

source("testenv.R")
ttt2()
xxx <- structure(1, class="xxx")
ttt(xxx)

test2.R 使用“附加”方法。 ttt2 仍然有效(並向控制台打印“x”),但直接函數調用失敗:

Error in UseMethod("ttt") : 
  no applicable method for 'ttt' applied to an object of class "xxx"

但是,不帶參數調用 ttt 和 ttt.xxx 表明它們是已知的,ls(pos=2) 表明它們在搜索路徑上,而 sloop::s3_dispatch(ttt(xxx)) 告訴我它應該有效。

這個問題與Confusion about UseMethod search mechanism和其中的鏈接https://blog.thatbuthow.com/how-r-searches-and-finds-stuff/有關,但我無法理解正在發生的事情:為什么它不工作嗎?我怎樣才能讓它工作。

我在 shell 中嘗試了 R Studio 和 R。

更新:根據以下答案,我將 testenv.R 更改為:

test_env <- new.env(parent=globalenv()) 
source("testfun.R", local=test_env) 
attach(test_env)
if (is.null(.__S3MethodsTable__.))
  .__S3MethodsTable__. <- new.env(parent = baseenv())
for (func in grep(".", ls(envir = test_env), fixed = TRUE, value = TRUE))
  .__S3MethodsTable__.[[func]] <- test_env[[func]]
rm(test_env, func)

... 這有效(我只使用“。”作為 S3 調度分隔符)。

首先,我的建議是:不要嘗試重新發明 R 程序包。 他們解決了您所說的您正在努力解決的所有問題,以及其他問題。

其次,我將嘗試解釋test2.R中出了什么問題。 它在xxx對象上調用ttt ,而ttt.xxx在搜索列表中,但未找到。

問題在於如何搜索ttt.xxx 搜索不會在搜索列表中查找ttt.xxx ,而是在調用ttt的環境中查找,然后在名為.__S3MethodsTable__. . 我認為這有兩個原因:

  • 首先,它要快得多。 它只需要在一兩個地方查找,並且可以在附加或分離包時更新表,這是一種相對少見的操作。

  • 第二,它更可靠。 每個包都有自己的方法表,因為兩個包可以對彼此無關的泛型使用相同的名稱,或者可以使用相同的不相關的類名。 所以包代碼需要能夠首先找到自己的定義。

由於您對ttt()的調用發生在頂層,因此 R 首先查找ttt.xxx()的位置,但它不在那里。 然后它會在全局.__S3MethodsTable__. (實際上在基礎環境中),它也不在那里。 所以它失敗了。

有一種解決方法可以使您的代碼正常工作。 如果你跑

.__S3MethodsTable__. <- list2env(list(ttt.xxx = ttt.xxx))

作為 testenv.R 的最后一行,您將在全局環境中創建一個方法表。 (通常那里沒有,因為那是用戶空間,除非用戶要求,否則 R 不喜歡把東西放在那里。)R 會找到那個方法表,並會找到它定義的ttt.xxx方法。 如果這破壞了 S3 調度的某些其他方面,我不會感到驚訝,所以我不建議這樣做,但如果您堅持要重新發明包系統,請嘗試一下。

一個鮮為人知的事實是,您必須使用.S3method()為自定義環境(包外)內的 S3 泛型定義方法 1幾乎沒有人知道這是因為在全球環境中沒有必要; 但自 R 版本 3.6 以來,它在其他任何地方都是必需的。

幾乎沒有關於此更改的文檔,只有Kurt Hornik 發表的一篇關於一些背景的技術博客文章 請注意,博客文章說更改是在 R 3.5.0 中進行的; 但是,您觀察到的實際效果——不再在附加環境中搜索 S3 方法——只是從 R 3.6.0 開始發生; 在此之前,它不知何故還沒有激活。

…除了只使用.S3method不會修復你的代碼,因為你的調用環境是全局環境。 我不明白這不起作用的確切原因,我懷疑這是由於 R 的 S3 方法查找中的一個細微錯誤。 事實上,使用getS3method('ttt', 'xxx')確實有效,即使它應該具有與實際 S3 方法查找相同的行為。

我發現完成這項工作的唯一方法是將以下內容添加到testenv.R

if (is.null(.__S3MethodsTable__.)) {
    .__S3MethodsTable__. <- new.env(parent = baseenv())
}

.__S3MethodsTable__.$ttt.xxx <- ttt.xxx

...換句話說:手動提供.GlobalEnv和 S3 方法查找表。 不幸的是,這依賴於一個未記錄的 S3 實現細節,理論上將來可能會發生變化。

或者,如果您使用'box' 模塊而不是source ,它“就可以工作”。 也就是說,您可以通過以下方式替換整個testenv.R

box::use(./testfun[...])

此代碼將testfun.R視為本地模塊並加載它,附加所有導出的名稱(通過附加聲明[...] )。


1 (在包內部,您需要使用等效的S3method命名空間聲明,但如果您使用的是“roxygen2”,那么它會為您處理)

暫無
暫無

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

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