动机
我正在开发一个 R 包(调用它pkg
),它在运行缓存中收集一些由其函数调用生成的对象。list
将缓存实现为命名空间中的对象(调用它.cache
)非常简单pkg
。但是,我想防止用户直接干扰缓存,它总是可以通过:::
语法暴露:pkg:::.cache
.
也就是说,我想保护.cache
类似于面向对象编程中的私有字段。所有操作.cache
都应由内部辅助函数完成,这些辅助函数由@export
具有“API 风格”的 ed 函数专门调用。
问题
因此,我有在命名空间中定义一个environment
对象(称为它.vault
)的想法,所以我可以......把 my.cache
放在我的.vault
.
我的困惑是,像这样的环境.vault
是 R 中为数不多的通过引用起作用的东西之一。所以我担心pkg:::.vault
会暴露.vault
其内容或对其内容进行修改和查看。我可以改变我的访问器功能,pkg:::.get_cache()
这样它们.vault
就不会启用这种暴露,但是如果可以直接就地修改,所有这些努力都是徒劳的。
别跑
例如,考虑下面这个危险的(?) 代码。首先,我从、 到“识别目标”检查内部.S3MethodsClasses
环境:dplyr
dplyr:::.S3MethodsClasses
#> <environment: 0x7fab9c6c5310>
ls(dplyr:::.S3MethodsClasses)
#> [1] "grouped_df" "rowwise_df"
然后我检查该rowwise_df
对象并认为它是一个有价值的“目标”:
dplyr:::.S3MethodsClasses$rowwise_df
#> Virtual Class "rowwise_df" [package "dplyr"]
#>
#> Slots:
#>
#> Name: .Data names row.names
#> Class: list character data.frameRowLabels
#>
#> Name: .S3Class
#> Class: character
#>
#> Extends:
#> Class "tbl_df", directly
#> Class "tbl", by class "tbl_df", distance 2
# ...
最后,我利用了这样一个事实,即与dplyr
命名空间中的大多数对象不同......
library(dplyr)
mutate <- NULL
dplyr::mutate
#> function (.data, ...)
#> {
#> UseMethod("mutate")
#> }
#> <bytecode: 0x7fab9c2f6120>
#> <environment: namespace:dplyr>
...一个像.S3MethodsClasses
点引用这样的环境,所以它的内容可以很容易地就地修改:
# "Copy" the pointer to allow `<-` assignment.
same_pointer <- dplyr:::.S3MethodsClasses
same_pointer
#> <environment: 0x7fab9c6c5310>
# Modify in place.
same_pointer$rowwise_df <- NULL
dplyr:::.S3MethodsClasses$rowwise_df
#> NULL
就这样,“金库”被抢劫了!
怀疑
我怀疑答案可能就在这里,lockEnvironment()
和朋友一起,但应用程序有点超出我的能力。也许我可以做一些事情.onLoad
来设置.vault
环境,然后它会被lock*()
编辑——它.vault
本身和其中的绑定——但不是在和一个访问器函数之间建立一个活动绑定之前,它将填充在命名空间中。.cache
.get_cache()
pkg
笔记
我已经在开发一个功能来终止(帮助)函数,比如.get_cache()
当它们在命名空间的同伴函数之外被调用时pkg
。因此,曝光pkg:::.get_cache()
不会让用户.get_cache()
手动操作或在他们自己的自定义功能中操作。
正规
我特别感谢 R 开发人员的建议,他们有足够的经验提供一个规范的答案(如果有的话)。