7 环境
7.1 小知识点
函数的环境分为四种:
- 封闭环境:封闭环境就是创建函数的环境,每个函数有且只有一个封闭环境
- 执行环境:调用函数创建了一个临时的执行环境,在函数执行结束后它被立即销毁
- 调用环境:每个执行环境都与一个调用环境相关联,它说明函数在哪里被调用
- 绑定环境:使用
<-
将函数与一个名字绑定在一起,这样就定义了一个绑定环境
每个环境都有父环境,父环境的存在是为了实现词法作用域(lexical scoping),即如果一个对象在当前环境中找不到,R就会在它的所有父环境中去寻找,直至找到为止;只有空环境没有父环境。
环境之间只有向上的父关系,它们之间没有反向链接,即给定一个环境我们无法找到它的子环境。
parent.frame()
函数给出括号内函数的调用环境。四个特殊环境:
globalenv()
,全局环境,它的父环境是最后一个加载的包baseenv()
,基础环境,R基础软件包的环境,它的父环境是空环境emptyenv()
,空环境environment()
,当前环境
search()
列出全局环境的所有父环境,这称为搜索路径。as.environment()
用来访问搜索列表中的任何环境。环境的搜索路径可以表示为:
parent.env()
用来查看某个环境的父环境。ls()
默认不显示以“.”开头的名字,可以通过设置all.names = TRUE
来显示。$
与[[]]
只在一个环境中查找,如果不存在与该名字绑定的值就返回NULL
;但get()
使用普通的作用域法则,会在当前环境及所有父环境中查找。在环境中删除对象不能像在列表中删除对象一样将其设置为
NULL
,因为这样会创建一个对NULL
的新绑定,使用rm()
,例:可以使用
exists()
来确定一个绑定关系是否存在,与get()
一样,它们都是遵循词法作用域原则的,如果不希望在父环境中查找,可以设置inherits = FALSE
。使用
identical()
来对两个环境进行比较。给定一个名字,
pryr::where()
会使用词法作用域原则找到定义这个名字的环境,默认从函数的调用环境开始,因为函数在执行时会产生执行环境,每个执行环境有两个父环境,一个调用环境与一个封闭环境,R的词法作用域法则只使用封闭父环境,如果不设置从调用环境开始,调用环境中的所有对象都查找不到。使用循环比使用函数递归要快,因为减少了函数的调用次数。
每个软件包都有两个与它相关联的环境:命名空间环境和软件包环境。
一个软件包中的函数是在命名空间中创建的,所以这个函数的封闭环境指向命名空间,同时这个函数还有两个绑定环境,分别存在于命名空间环境中和软件包环境中。
一个软件包中的函数,如果它的定义使用了其他函数,那么它在查找这个函数时首先会在命名空间环境中查找,并且永远不会在
globalenv()
中查找,所以我们在全局环境中自定义的同名函数不会影响到包中的函数。查找流程可以表示为:函数 –> 本包的命名空间环境 –> 其它加载包的命名空间环境 –> base包的命名空间环境
对于存在嵌套的函数,父函数的执行环境就是子函数的封闭环境。
非常有用的函数:
- 延时绑定:不立即将结果赋值给对象,它创建和存储一个约定(promise,即未被求解的参数),在需要时对约定中的表达式进行求值。使用
pryr::"%<d-%"
,它是对基础包中的delayedAssign()
的包装。 - 主动绑定:不是绑定到常量对象,而是在每次访问时都要重新计算,用来实现引用类字段;暂时没想到使用场景。使用
pryr::"%<a-%"
,它是对基础包中的makeActiveBinding()
的包装。
- 延时绑定:不立即将结果赋值给对象,它创建和存储一个约定(promise,即未被求解的参数),在需要时对约定中的表达式进行求值。使用
对于非常大的键值对数据,使用hashmap来存储提取效率非常高,因为hashmap根据名称查找对象的时间复杂度为O(1),它实际上是通过数组角标来定位数据的,每一个键值对在存入hashmap时会经过一个转化函数
f(x)
得到一个位置,键值对随即被存入这个位置中,在没有位置冲突的hashmap中查找的时间复杂度的确为O(1)。有一个名为
fastmap
的库实现了快速的键值对存储。