专注系列化、高质量的R语言教程
在准备R6
工具包的相关内容时,发现需要一些关于“环境”的知识,所以在这里加以介绍。主要参考资料有:
1、http://adv-r.had.co.nz/Environments.html[1]
2、https://adv-r.hadley.nz/environments.html[2]
3、https://www.likecs.com/ask-5533633.html[3]
本篇目录如下:
0 引子
1 环境
1.1 环境与列表的区别
1.2 父环境
1.3 定义环境对象
1.4 检索环境的元素
0 引子
在正式介绍相关内容前,读者可以思考如下问题:
如果函数A
依赖于函数B
进行定义,那么如果用户在全局环境中自定义了一个名称也为B
的函数,是否会对函数A
造成影响?为了加以区分,我们分别记两个B
函数为B1
、B2
(实际函数名相同)。可以分如下几种情况:
1、函数A
和B1
位于同一个工具包中;
2、函数A
和B1
位于不同工具包中;
3、函数A
位于全局环境,函数B1
位于某个工具包中;
4、函数A
和B1
都位于全局环境中。
凭借经验我们可以作以下回答:
如果用户自定义的函数会影响工具包自有的函数,那么R语言的设计就是失败的。因此推测情况1不太可能会影响。
情况2不太好判断。通常情况下,工具包调用其他工具包中的函数会使用包名作为前缀,如
dplyr::select()
,这种情况函数有明确的指向,也不太可能会影响。情况3函数
A
位于全局环境,说明也是自定义函数,调用B
函数时如果没有使用包名作为前缀的话很可能会影响。若
B1
位于全局环境中,会被后定义的B2
替换,情况4显然会影响。
这个问题的答案将会在下篇揭晓,本篇接下来会介绍一些关于环境的基础知识,下篇则主要介绍函数环境。
1 环境
1.1 环境与列表的区别
按照Hadley Wickham的说法:The environment is the data structure that powers scoping.
因此,“环境”本身也是一种数据结构,它可以容纳其他数据结构,如数据框、列表等作为它的元素,并使函数能够运行。
从中我们可以发现,环境和前面介绍的proto对象很类似,它的元素包含两类:变量和函数。实际上,proto对象的数据结构形式就是“环境”。
library(proto)
p <- proto()
class(p)
## [1] "proto" "environment"
前面还介绍到,proto对象和列表(list)的定义方式类似。结合Hadley Wickham的说法,环境和列表的主要区别如下:
1、环境中每个元素的名称都是唯一的。
尽管列表可以根据名称查找元素,但主要还是根据序号区分元素的,不同元素可以重名;再者,列表不强制元素必须有名称。
l <- list(a = 2, a = 3, 4)
l
## $a
## [1] 2
##
## $a
## [1] 3
##
## [[3]]
## [1] 4
2、环境的元素是不区分排列顺序的,这一点类似于集合。
3、环境有它的父环境(An environment has a parent)。
4、环境具有引用语义(Environments have reference semantics)。
这一点与普通数据结构有很大区别。例如,我们不能引用列表的一个元素去定义另一个元素:
l <- list(a = 2, b = a + 2)
## Error: object 'a' not found
而环境对象可以这么做:
p <- proto(expr = {
a = 2
b = a + 2})
str(p)
## proto object
## $ a: num 2
## $ b: num 4
1.2 父环境
我们最熟悉的环境是“全局环境”。
这是默认的工作环境,前面定义的列表l
和proto对象p
就是在其中定义的,可以看作是它的元素。点击上图中红色框内的倒三角,可以查看当前加载的其他环境:
可以看到,这些环境的名称是以package:
开头,然后接上对应的工具包名称。可以推测,每个工具包都对应着一个环境(下篇会介绍每个工具包实际对应两个环境)。
这些环境的排列是有意义的:下面的环境是上面环境的“父环境”(parent environment)。例如,对于上图,全局环境的父环境是package:proto
,package:proto
的父环境是package:ggplot2
,依次类推,最后一个环境是package:base
,package:base
的父环境是空环境(empty environment),空环境是有且仅有的一个没有父环境的环境。
工具包对应的环境是按加载顺序排列的。每加载一个新工具包,它对应的环境就插到全局环境和上次加载的工具包环境之间。因此,全局环境的父环境总是最新加载的工具包的环境。
基础包不仅仅是base
包,而是默认情况下会随R启动自动加载的工具包,正因为如此它们的环境总排在最下方。这些基础包的环境从下到上顺序是:package:base
、package:methods
、package:datasets
、package:utils
、package:grDevices
、package:graphics
、package:stats
。
使用search()
函数可以输出当前加载的环境(按顺序排列):
search()
## [1] ".GlobalEnv" "package:proto" "package:stats"
## [4] "package:graphics" "package:grDevices" "package:utils"
## [7] "package:datasets" "package:methods" "Autoloads"
## [10] "package:base"
点击上图中一个工具包的环境,还可以查看它的元素:Data、Values、Functions。
1.3 定义环境对象
正如定义proto一样,我们可以自定义一个环境对象:
e <- new.env()
e$a <- FALSE
e$b <- "a"
e$c <- 2.3
e$d <- 1:3
与工具包环境不同,自定义环境并不是插到全局环境和新加载工具包环境之间。因为它默认是在全局环境中定义的,因此可以看作是全局环境的元素,全局环境也就是它的父环境。
使用parent.env()
函数可以查看一个环境的父环境:
parent.env(e)
## <environment: R_GlobalEnv>
使用parent
参数可以自定义父环境:
e2 <- new.env(parent = emptyenv())
parent.env(e2)
## <environment: R_EmptyEnv>
emptyenv()
表示空环境。此外,globalenv()
表示全局环境,baseenv()
表示package:base
。
<-
与<<-
的区别
<-
表示在当前环境中创建对象;<<-
表示在当前环境的父环境中修改或创建对象。
with(e, p <<- 1)
p
## [1] 1
with(e, f <<- 2)
f
## [1] 2
p
先前是全局环境中的一个proto对象;f
为新创建对象。
1.4 检索环境的元素
使用元素名称检索
环境的元素本身就是一个对象。当我们需要调用一个对象时,R会根据对象名称首先在当前环境(current environment)中进行检索,若检索不到则在它的父环境中继续检索,仍检索不到则在父环境的父环境中检索,以此类推,直至package:base
,若仍检索不到,则报错。这也是为什么新加载工具包中的函数会覆盖同名函数的原因。
使用environment()
函数可以查看当前环境:
environment()
## <environment: R_GlobalEnv>
也可以查看某个函数所在的环境:
library(dplyr)
environment(fun = select)
## <environment: namespace:dplyr>
上面代码输出的环境名称以
namespace:
为前缀,它与以package:
为前缀的环境的区别见下篇。
get函数
如果要在特定环境中进行检索,可以使用$
、[[
符号和get()
函数进行检索。
$
、[[
符号只会在指定环境中检索,若检索不到,则返回NULL:
e$a
e[["a"]]
## [1] FALSE
e[["l"]]
## NULL
注意:环境元素是无序的,因此不能使用
e[[1]]
、e[[2]]
这种形式检索元素。
get()
函数若在指定环境中检索不到,默认情况下还会向它的父环境以及祖环境中继续检索:
get("a", envir = e)
## [1] FALSE
get("p", envir = e)
## <environment: 0x000002213c308cf0>
## attr(,"class")
## [1] "proto" "environment"
我们先前在全局环境中定义了对象
p
,因为全局环境是环境e
的父环境,因此使用get()
函数可以检索到p
。
若想限制get()
函数只在指定环境中查找,可以设置inherits
参数为FALSE
。使用get()
函数若查找不到元素,会直接报错:
get("p", envir = e, inherits = F)
## Error in get("p", envir = e, inherits = F) : object 'p' not found
exists函数
exists()
函数用法与get()
函数类似,但它返回的是逻辑值。若元素存在返回TRUE
,否则返回FALSE
。
exists("p", envir = e)
## [1] TRUE
exists("p", envir = e, inherits = F)
## [1] FALSE
参考资料
[1]
Advanced R (1st Edition) - Environments: http://adv-r.had.co.nz/Environments.html
[2]Advanced R (2nd Edition) - Environments: https://adv-r.hadley.nz/environments.html
[3]论坛问答: https://www.likecs.com/ask-5533633.html