R语言基础 | 环境Environment(下)——函数环境


专注系列化、高质量的R语言教程

(本号已支持快捷转载,无需白名单即可转载)


本篇接上篇。主要参考资料有:

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]

本篇目录如下:

  • 2 函数环境

    • 2.1 绑定环境

    • 2.2 封闭环境

    • 2.3 执行环境

    • 2.4 调用环境

  • 3 环境作为数据结构

2 函数环境

通过一个简单的例子,就可以理解函数有自己的环境:

x <- 2

f <- function(y) {
  x <- 1
  y + x
}

如上代码,在函数定义式之外存在一个变量x <- 2(全局环境),函数定义式内部也有一个同名变量x <- 1。显然,函数在运行时会使用内部的x变量,而且不会改变外部x变量的取值。

f(1)
## [1] 2 

x
## [1] 2

而如果函数内部没有定义x,则运行时会使用外部的x

f <- function(y) {
  y + x
}

f(1)
## [1] 3

这符合上篇介绍的环境的元素检索特点:首先会在当前环境中检索,检索不到则向父环境中检索。从中我们也可以推测,全局环境应当是这个函数环境(function environment)的父环境。

上述推测不完全准确。实际上,函数存在四个环境:绑定环境、封闭环境、执行环境和调用环境。上例中我们感受到的函数环境,实际是执行环境。

2.1 绑定环境

函数作为一个对象,本身就是一个环境的元素,这个环境称为函数的绑定环境(binding environment)。如上例中的f位于全局环境中,则全局环境就是它的绑定环境。find()函数可以根据名称查找函数的绑定环境:

find("f")
## [1] ".GlobalEnv"

pryr工具包的where()是专门查找函数绑定环境的函数:

library(pryr)
where("f")
## <environment: R_GlobalEnv>

对于工具包的函数而言,它们的绑定环境是package:*

library(dplyr)
find("select")
## [1] "package:dplyr"

2.2 封闭环境

函数的封闭环境(enclosing environment)不太容易理解,但却十分重要,它能解决我们在上篇“引子”中所提出的问题。简而言之,封闭环境就是函数在被最初定义时所对应的当前环境(current environment),它具有不可变性,即不会随着函数所处位置的变化而变化。

使用environment()函数可以查看函数的封闭环境。对于上例中的f而言,它的封闭环境仍然是全局环境:

environment(f)
## <environment: R_GlobalEnv>

对于工具包的函数而言,它们的封闭环境是namespace:*

environment(select) 
## <environment: namespace:dplyr>

函数的封闭环境和绑定环境有何区别呢?或者说,对于每个工具包而言,R为何要为其设计两个环境?

很容易理解,函数是哪个环境的元素,它的绑定环境就是哪个环境,因此上述问题就转变成封闭环境存在的必要性了。

sd()函数为例,它的作用是计算序列的标准差,位于基础包stats中,定义式如下:

sd
## function (x, na.rm = FALSE) 
## sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), 
##     na.rm = na.rm))
## <bytecode: 0x0000021607f94ca0>
## <environment: namespace:stats>

而函数是可以进行传递的:

g <- sd

注意:sd后不能带括号。

这样我们实际上就在全局环境中增加了一个和sd()功能一致的函数g,而g绑定环境显然是全局环境,不同于sd()

find("g")
## [1] ".GlobalEnv"

但是从功能上看,g的定义式是在stats工具包中完成的,它和sd()的封闭环境仍然相同:

environment(g)
## <environment: namespace:stats>

从定义上可以看出,sd()依赖于var()等函数。现在,我们在全局环境中重新定义一个var()函数,它并不会影响gsd()函数的运行结果:

var <- function() 1

sd(1:4)
## [1] 1.290994
g(1:4)
## [1] 1.290994

因此可以说,封闭环境的不变性保证了函数功能在传递时的一致性,不过这种一致性还得依靠执行环境来完成。

2.3 执行环境

执行环境(execution environment)是函数在调用时所形成的环境,一旦函数运行结束,执行环境就不存在了。

函数的封闭环境是执行环境的父环境;如果在定义一个函数(子函数)时使用了另一个函数(父函数),那么父函数的执行环境就是子函数的封闭环境。这也就是为什么在上例中,函数g在运行时会向封闭环境namespace:stats中查找var()函数,而不是使用全局环境所定义的var()函数。至此,我们就可以回答“引子”中问题了:前两种情况不会影响,后两者情况会影响。

2.4 调用环境

调用环境(calling environment)可以理解成函数在运行时的外部环境。使用parent.frame()函数可以查看调用环境(注意区分与parent.env()函数)。前面的例子中,全局环境的var()函数就位于调用环境中。

下例中,x <- 1位于函数的执行环境中,而x <- 2就位于函数的调用环境中:

x <- 2

f <- function(y) {
  x <- 1 
  
  x1 <- get("x", environment())
  x2 <- get("x", parent.frame())
  
  if (y == 1) return(x1)
  if (y == 2) return(x2)
}

f(1)
## [1] 1
f(2)
## [1] 2

3 环境作为数据结构

环境本身就是一种数据结构,前面介绍的proto对象就是一种环境。

对于普通数据结构来说,修改取值实际是通过重新定义一个同名对象来完成的:

x <- 1
x <- x + 1

x
## [1] 2

如果你不把修改值再赋给x,那么程序运行完后就会把结果回收而不会改变对象值:

x <- 1
x + 1

x
## [1] 1

而环境对象则不然,这一点在proto对象中已有体现:

e <- new.env()
e$x <- 1:4
e$f <- function() {e$x = e$x + 1}

e$f()
e$x
## [1] 2 3 4 5

按照Hadley Wickham的说法,环境相比于普通数据结构有如下三个作用[4]

  • 避免通过备份来修改对象值(Avoiding copies of large data);

  • 更好管理工具包的函数(Managing state within a package);

  • 绘制哈希图(As a hashmap)。

参考资料

[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

[4]

Environment as data structures: https://adv-r.hadley.nz/environments.html#explicit-envs

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值