You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

R ReferenceClass子类定义中调用父类方法的缓存实现问题

解决R中ReferenceClass对象专属方法缓存的问题

针对你遇到的cache方法找不到的报错,以及对象专属缓存的需求,这里提供几种可行的解决方案:

方案1:修复原代码的装饰器引用问题

原报错是因为cache是父类的实例方法,在子类定义阶段无法直接访问。可以通过Cache$methods$cache直接获取父类的方法函数作为装饰器使用:

# 定义中缀装饰器函数
`%decorate%` = function(decorator, f) {
  decorator(f)
}

# Cache父类
Cache = 
  setRefClass(
    'Cache',
    fields = 
      c(  
        # 私有:输入到输出的哈希映射
        "prev_output_hash_map__"
      ),
    methods = 
      list(
        initialize =
          function(...) {
            callSuper(
              prev_output_hash_map__ = r2r::hashmap(),
              ...
            )
          },

        cache =
          function(f) {
            function(refresh = FALSE, ...) {              
              # 哈希函数代码和输入参数
              input_hash__ =
                rlang::hash(
                  list(
                    deparse1(f),
                    ...
                  )
                )
              # 检查缓存是否存在或需要刷新
              if(is.null(prev_output_hash_map__[input_hash__][[1]]) || refresh) {
                prev_output_hash_map__[[input_hash__]] <<- f(...)
              }
              return(prev_output_hash_map__[input_hash__][[1]])
            }
          }
      )
  )

# 子类:使用父类方法定义作为装饰器
CachingObject = 
  setRefClass(
    "CachingObject",
    contains = "Cache",
    methods = 
      list(
        long_running_function = 
          Cache$methods$cache %decorate% function(input1, input2) {
            Sys.sleep(10)
            return(input1 + input2)
          }
      )
  )

说明

Cache$methods$cache直接获取父类定义的cache方法函数,解决了子类定义阶段找不到cache的问题。当实例调用方法时,ReferenceClass会自动将方法绑定到实例环境,确保prev_output_hash_map__指向当前实例的私有字段,实现对象专属缓存。

方案2:拆分缓存逻辑为通用执行函数(更清晰的结构)

放弃装饰器,改用父类提供的通用缓存执行方法,将业务逻辑与缓存逻辑分离:

library(r2r)
library(rlang)

# Cache父类:提供通用缓存能力
Cache = setRefClass(
  'Cache',
  fields = list(
    prev_output_hash_map__ = "hashmap"
  ),
  methods = list(
    initialize = function(...) {
      prev_output_hash_map__ = hashmap()
      callSuper(...)
    },
    
    # 通用缓存执行函数:处理缓存逻辑
    cached_exec = function(func_name, ..., refresh = FALSE) {
      input_hash = rlang::hash(list(func_name, ...))
      
      if (is.null(prev_output_hash_map__[[input_hash]]) || refresh) {
        # 调用对应的私有方法执行实际逻辑
        result = do.call(paste0(func_name, "__"), list(...), envir = .self)
        prev_output_hash_map__[[input_hash]] = result
      }
      
      prev_output_hash_map__[[input_hash]]
    }
  )
)

# 子类:实现具体业务方法
CachingObject = setRefClass(
  "CachingObject",
  contains = "Cache",
  methods = list(
    # 私有方法:实际耗时逻辑
    long_running_function__ = function(input1, input2) {
      Sys.sleep(10)
      input1 + input2
    },
    
    # 公开方法:调用缓存执行函数
    long_running_function = function(input1, input2, refresh = FALSE) {
      .self$cached_exec("long_running_function", input1 = input1, input2 = input2, refresh = refresh)
    }
  )
)

测试验证

# 实例1的缓存独立
obj1 = CachingObject$new()
obj1$long_running_function(1, 2)  # 首次调用等待10秒
obj1$long_running_function(1, 2)  # 后续调用立即返回结果

# 实例2的缓存与实例1完全隔离
obj2 = CachingObject$new()
obj2$long_running_function(1, 2)  # 仍需等待10秒

这个方案结构清晰,避免了装饰器的环境绑定问题,同时确保每个实例的缓存完全独立。

方案3:使用memoise包实现实例专属缓存

如果不想手动实现缓存逻辑,可以借助memoise包,为每个实例创建独立的缓存环境:

library(memoise)

Cache = setRefClass(
  'Cache',
  fields = list(
    # 每个实例的专属缓存环境
    cache_env__ = "environment"
  ),
  methods = list(
    initialize = function(...) {
      # 创建独立缓存环境
      cache_env__ = new.env(parent = emptyenv())
      callSuper(...)
    },
    
    # 生成实例专属的缓存装饰器
    get_cacher = function() {
      function(f) {
        memoise(f, cache = cache_env__)
      }
    }
  )
)

CachingObject = setRefClass(
  "CachingObject",
  contains = "Cache",
  methods = list(
    initialize = function(...) {
      callSuper(...)
      # 初始化时用实例专属缓存装饰方法
      .self$long_running_function = .self$get_cacher()(function(input1, input2) {
        Sys.sleep(10)
        input1 + input2
      })
    },
    # 占位方法,会被初始化时的缓存函数覆盖
    long_running_function = function(input1, input2) {
      stop("Method not initialized")
    }
  )
)

这个方案利用成熟的缓存库实现功能,同时通过独立环境保证每个实例的缓存不共享。

内容的提问来源于stack exchange,提问作者Canadian_Marine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 20:30:56