R语言基础 | 环境Environment(上)


专注系列化、高质量的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函数为B1B2(实际函数名相同)。可以分如下几种情况:

1、函数AB1位于同一个工具包中;
2、函数AB1位于不同工具包中;
3、函数A位于全局环境,函数B1位于某个工具包中;
4、函数AB1都位于全局环境中。

凭借经验我们可以作以下回答:

  • 如果用户自定义的函数会影响工具包自有的函数,那么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 父环境

我们最熟悉的环境是“全局环境”。

0ed19e174a19b3f1f8602e6d58286cc3.png

这是默认的工作环境,前面定义的列表l和proto对象p就是在其中定义的,可以看作是它的元素。点击上图中红色框内的倒三角,可以查看当前加载的其他环境:

69e5c653e17b1551d87ebcecd8b7532b.png

可以看到,这些环境的名称是以package:开头,然后接上对应的工具包名称。可以推测,每个工具包都对应着一个环境(下篇会介绍每个工具包实际对应两个环境)。

这些环境的排列是有意义的:下面的环境是上面环境的“父环境”(parent environment)。例如,对于上图,全局环境的父环境是package:protopackage:proto的父环境是package:ggplot2,依次类推,最后一个环境是package:basepackage:base的父环境是空环境(empty environment),空环境是有且仅有的一个没有父环境的环境。

工具包对应的环境是按加载顺序排列的。每加载一个新工具包,它对应的环境就插到全局环境和上次加载的工具包环境之间。因此,全局环境的父环境总是最新加载的工具包的环境。

基础包不仅仅是base包,而是默认情况下会随R启动自动加载的工具包,正因为如此它们的环境总排在最下方。这些基础包的环境从下到上顺序是:package:basepackage:methodspackage:datasetspackage:utilspackage:grDevicespackage:graphicspackage: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。

66855f88705aced70d0f9fecf3a65bd7.png

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值