R语言S4类循环生成Getter方法出现返回值不符问题
R语言S4类循环创建Getter方法时的闭包环境问题
尝试通过for循环为S4类的槽位批量创建Getter方法,但测试发现所有Getter都返回最后一个槽位的值——比如调用a(o)得到的是"b"而非预期的"a"。
问题原因
这是R的惰性求值和闭包环境共享导致的问题:for循环中的变量e处于同一个环境中,当get_slot(e)生成的函数被调用时,才会去解析slot变量的取值,此时循环已经执行完毕,e的值固定为最后一个元素"b",因此所有Getter方法实际上都在访问b槽。
解决方案
核心是让每个闭包捕获当前循环迭代的slot值,而非共享同一个变量引用,以下是三种可行方案:
方案1:在闭包中强制求值slot变量
修改get_slot函数,用force()强制立即求值slot参数,将当前值绑定到闭包环境中:
library(methods) library(testthat) get_slot <- function(slot) { force(slot) # 强制求值,捕获当前slot的具体值 function(x) slot(x, slot) } setClass("TEST", slots = list(a = "character", b = "character")) for (e in c("a", "b")) { def <- eval(parse(text = sprintf("function(x) standardGeneric('%s')", e))) setGeneric(e, def = def) setMethod(e, "TEST", get_slot(e)) } test_that("getters work", { o <- new("TEST") o@a <- "a" o@b <- "b" expect_equal(a(o), "a") expect_equal(b(o), "b") }) #> Test passed 🎉
方案2:使用lapply替代for循环
lapply的每个迭代都拥有独立的环境,不会共享循环变量,天然避免这个问题:
library(methods) library(testthat) get_slot <- function(slot) { function(x) slot(x, slot) } setClass("TEST", slots = list(a = "character", b = "character")) lapply(c("a", "b"), function(e) { def <- eval(parse(text = sprintf("function(x) standardGeneric('%s')", e))) setGeneric(e, def = def) setMethod(e, "TEST", get_slot(e)) }) test_that("getters work", { o <- new("TEST") o@a <- "a" o@b <- "b" expect_equal(a(o), "a") expect_equal(b(o), "b") }) #> Test passed 🎉
方案3:直接在循环内生成函数体
直接将当前槽位名称写入函数体,彻底避免闭包引用问题:
library(methods) library(testthat) setClass("TEST", slots = list(a = "character", b = "character")) for (e in c("a", "b")) { def <- eval(parse(text = sprintf("function(x) standardGeneric('%s')", e))) setGeneric(e, def = def) # 直接生成访问对应槽的函数 setMethod(e, "TEST", eval(parse(text = sprintf("function(x) slot(x, '%s')", e)))) } test_that("getters work", { o <- new("TEST") o@a <- "a" o@b <- "b" expect_equal(a(o), "a") expect_equal(b(o), "b") }) #> Test passed 🎉
内容的提问来源于stack exchange,提问作者Julian Grimm
相关产品推荐
相关产品推荐

