当列表本身不改变地址时,lobstr::obj_addr 会出现奇怪的行为,这是由于其对列表进行矢量化所致
我刚刚开始学习 Wickham 的《高级 R》(第二版),并完成了 2.2.2 练习的第一个练习。我认为,鉴于:
a <- 1:10; b <- a; c <- b
它们都将具有与lobstr::obj_addr
函数检索到的相同的内存地址。如果我们仅使用 a、b 或 c 作为输入,情况确实如此,但由于我很懒,想一次获得所有值,所以我这样做了:
list(a, b, c) |> lapply(obj_addr) # lapply or sapply
然后,每次运行该函数时,我们都会在不同的名称中获得一组不同的值。如果我们x <- list(a, b, c)
在调用函数之前通过lapply
、 和进行设置,这种情况仍然会发生obj_addr(x[[1]]) == obj_addr(x[[2]]) == obj_addr(x[[3]]) == obj_addr(a)
,因此这不是每次都创建新列表的问题。有人知道这是怎么回事吗?我理解,在某个点上,每次调用都会生成一个具有自己内存地址的新输出对象,但我不知道如何lapply
干扰给定对象的常量函数,例如obj_addr
。
先感谢您!
这是由如何
lobstr
识别环境的一个错误引起的MrFlick 在评论中指出
x |> lapply(function(x) obj_addr(x))
返回正确答案,而x |> lapply(obj_addr)
返回错误答案。这表明环境中发生了一些奇怪的事情。问题是这是由lobstr::obj_addr()
或引起的lapply()
。事实证明,这是由于 的lobstr
使用方式rlang
导致它在错误的环境中寻找对象。做什么
lobstr::obj_addr()
?源代码(略微简化)如下:
obj_addr_()
是用 C 编写的获取内存地址的主力函数。但是,首先 Robj_addr()
使用rlang
quosure 工具来访问对象而不增加引用计数。这样可以确保没有对该对象的引用,从而允许垃圾收集稍后正常进行。但是,在 的情况下lapply(x, lobstr::obj_addr)
,它无法正确访问 元素的环境x
,这就是错误出现的地方。环境会发生什么变化
lapply()
?考虑以下函数来获取调用函数的对象的环境:
lapply()
我们可以通过两种方式调用它:第一组结果是有意义的。
lapply()
每次调用匿名函数(函数闭包)时都会创建一个临时环境。但是,在空环境中运行是没有意义的
lapply(x, f)
。我们知道可以用 引用全局环境中的对象lapply()
。但是空环境根据定义不包含任何对象,也没有父级:所以显然返回了错误的环境。让我们尝试使用 base R
rlang::quo_get_env(rlang::enquo(x))
查找调用函数的父环境:lapply()
这更有意义,并为我们了解正在发生的事情提供了线索。
编写自己的函数来获取指针
为了排除
lapply()
这种不一致的根源,让我们写出自己的版本,lobstr::obj_addr()
它不会弄乱环境。C级函数的相关行obj_addr_()
是将转换SEXP
为指针的地方:这是一个类似的函数,用于获取跳过内容的指针
rlang
:相比
get_pointer()
lobstr::obj_addr()
让我们
x
分别定义并检查地址:lobstr
现在我们可以比较三种方法的结果。我将使用sapply()
而不是,lapply()
因为它打印得更好。我们可以看到这sapply(x, lobstr::obj_addr)
是不正确的。问题是,如果我们跳过环境部分,我们是否能得到正确的结果。我们可以使用
get_pointer()
:因此
get_pointer()
两次都得到了正确的结果。这表明问题出在lobstr
rlang quosure 工具的使用上。我实际上不确定这是否是个rlang
问题,或者问题是否出在 如何lobstr
使用 上。但是,由于这两个包都是r-librlang
的一部分,我想提交给其中一个包的错误报告很快就会到达正确的地方。这种行为似乎是由于在检索其地址之前对参数
obj_addr
进行了处理(参见函数定义)。因此,我们可以独立于实际的地址检索部分来enquo
检查的行为。enquo
为了检查,我们可以定义一个基于
obj_addr
检索地址的函数:作为参考,该对象
a
位于:当位于闭包主体之外时,
enquo
返回一些无用的东西以检索对象的地址,因为enquo
似乎能够完全评估一个符号(例如与之相反substitute
)并返回一个新构造的对象:然而,在闭包内部,一切都按预期进行:
lapply
在构造调用期间评估其参数,并且,我们可能可以使用以下方式模拟其行为force
(以使其更清楚):因为
enquo
不再返回可搜索的符号。正如 MrFlick 在评论中指出的那样,用另一层闭包进行包装可以达到预期的效果,因为
enquo
似乎无法对其论点进行全面的评估:此外,举个例子,如果我们重新定义
obj_addr
如下:那么
lapply
就不会造成混淆: