R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。
R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。
要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。
关于作者:
- 张丹(Conan), 程序员Java,R,PHP,Javascript
- weibo:@Conan_Z
- blog: http://blog.fens.me
- email: bsspirit@gmail.com
转载请注明出处:
http://blog.fens.me/r-environments-function/
前言
本文接上一篇文章揭开R语言中环境空间的神秘面纱,继续介绍R语言中函数的环境空间。
R语言的函数环境空间,具有动态性,可以让我们用更少的代码构建出更复杂的应用来。
目录
- R语言的函数环境空间
- 封闭环境
- 绑定环境
- 运行环境
- 调用环境
- 完整的环境操作
1. R语言的函数环境空间
在R语言中,变量、对象、函数都存在于环境空间中,而函数又可以有自己的环境空间,我们可以在函数内再定义变量、对象和函数,循环往复就形成了我们现在用的R语言环境系统。
一般情况,我们可以通过new.env()去创建一个环境空间,但更多的时候,我们使用的是函数环境空间。
函数环境空间,包括4方面的内容:
- 封闭环境,每个函数都有一个且只有一个封闭环境空间,指向函数定义的环境空间。
- 绑定环境,给函数指定一个名字,绑定到函数变量,如fun1<-function(){1}。
- 运行环境,当函数运行时,在内存中动态产生的环境空间,运行结束后,会自动销毁。
- 调用环境,是指在哪个环境中进行的方法调用,如fun1<-function(){fun2()},函数fun2在函数fun1被调用。
本文的系统环境
- Win7 64bit
- R: 3.0.1 x86_64-w64-mingw32/x64 b4bit
2. 封闭环境
封闭环境,比较好理解,是对函数空间的一个静态定义,在函数定义时指向所在的环境空间,通过environment()函数,来查看封闭环境。
我们在当前的环境空间,定义一个函数f1,查看f1函数的封闭环境为 R_GlobalEnv。
> y <- 1
> f1 <- function(x) x + y
> environment(f1)
<environment: R_GlobalEnv>
再定义一个函数f2,让f2函数调用f1函数,查看f2的函数的封闭环境为 R_GlobalEnv。
> f2 <- function(x){
+ f1()+y
+ }
> environment(f2)
<environment: R_GlobalEnv>
所以,封闭环境是在定义的时候设置的,与具体运行时环境,没有关系。
3. 绑定环境
绑定环境,就是把函数的定义和调用,通过函数变量连起来。
比如,我们新建一个环境空间e,在e定义一个函数g,就当相于把一个函数绑定到g变量,通过找到e环境空间中的g变量,就可以调用这个函数。
# 新建一个环境空间
> e <- new.env()
# 绑定一个函数到e$g
> e$g <- function() 1
# 查看函数g的定义
> e$g
function() 1
# 运行函数g
> e$g()
[1] 1
在环境空间e中,再定义一个嵌套函数e$f
# 绑定一个函数到e$f
> e$f <- function() {
+ function () 1
+ }
#查看函数f的定义
> e$f
function() {
function () 1
}
# 调用函数f,返回嵌套的匿名函数定义
> e$f()
function () 1
<environment: 0x000000000dbc0a28>
# 调用函数f,和嵌套匿名函数,得到结果
> e$f()()
[1] 1
查看函数g和f的封闭环境。
# 函数g和f的封闭环境
> environment(e$g)
<environment: R_GlobalEnv>
> environment(e$f)
<environment: R_GlobalEnv>
# f内部的匿名函数的封闭环境
> environment(e$f())
<environment: 0x000000000d90b0b0>
# 匿名函数的父函数的封闭环境
> parent.env(environment(e$f()))
<environment: R_GlobalEnv>
我们看到e$g和e$f的封闭环境,都是当前环境R_GlobalEnv。而e$f()的匿名函数的封闭环境,是当前环境的子环境,也就是e$f函数的环境空间。
4. 运行环境
运行环境,是函数被调用时,产生的内存环境。运行环境是临时的,当函数运行完成后,运行环境会被自动销毁。在运行环境中的,定义的变量、对象和函数,也是动态创建的,随着内存释放而销毁。
定义一个函数g,在函数g中,有临时变量a和参数x。
> g <- function(x) {
+ if (!exists("a", inherits = FALSE)) {
+ a<-1
+ }
+ a<-a+x
+ a
+ }
# 调用函数g
> g(10)
[1] 11
> g(10)
[1] 11
调用2次函数g,运行结果都是11。我们可以看出,变量a在g函数中为临时变量,没有进行持有化,每次都是新的。
增加一些输出信息,我们再来看看,这个函数的运行情况。
> g <- function(x) {
+ message("Runtime function") # 增加注释
+ print(environment()) # 打印运行时环境
+ if (!exists("a", inherits = FALSE)) {
+ a<-1
+ }
+ a<-a+x
+ a
+ }
# 调用函数g
> g(10)
Runtime function
<environment: 0x000000000e447380>
[1] 11
> g(10)
Runtime function
<environment: 0x000000000d2fa218>
[1] 11
我们还是调用2次g函数,看到print(environment()) 的输出,有2次不同的环境地址 0x000000000e447380,0x000000000d2fa218。说明函数的运行时环境,是内存临时分配的。
5. 调用环境
调用环境,是指函数是在哪个环境中被调用的。匿名函数通常是在定义的封闭环境中被调用。
我们定义一个嵌套的函数h,包括一个匿名函数。
> h <- function() {
+ x <- 5
+ function() {
+ x
+ }
+ }
# 调用函数h,把h函数内部的匿名函数赋值给r1
> r1 <- h()
# 在当前环境定义变量x
> x <- 10
# 调用函数r1
> r1()
[1] 5
r1函数运行后的结果为5,说明r1函数获得的是匿名函数查所在的封闭环境的x值,而不是r1变量所在的当前环境的x值。
我们把代码稍后修改,在函数h中,定义2个x变量。用<<-给第二个x变量赋值,相当于给父环境空间中的x变量赋值。
> h <- function() {
+ x <- 10
+ x <<- 5
+ function() {
+ x
+ }
+ }
# 调用函数h
> r1 <- h()
# 调用函数r1
> r1()
[1] 10
# 当前空间的变量x
> x
[1] 5
r1函数运行后的结果为10,说明r1函数获得的是匿名函数查所在的封闭环境的x值10,而不是通过<<-赋值的父环境中的x的值。
6. 完整的环境操作
接下来,把函数环境空间的操作放到一起,做一个完整环境的说明。
图片解释:
- 1. 蓝色圆圈,表示函数封闭,始终指向函数定义环境空间。
- 2. 粉色长方形,表示已加载的包环境空间,包括R_GlobalEnv, base, R_EmptyEnv等
- 3. 绿色长方形,表示已定义的函数环境空间。
- 4. 绿色长方形内的白色长方形,表示命名函数,包括fun1,fun2。
- 5. 不在绿色长方形内白色长方形,表示已定义的匿名函数。
- 6. 在长方形内的粉色小正方形,表示在环境空间中定义的变量,包括x,fx,f2。
- 7. 橙色小正方形,表示在内存中的变量值,包括5,2,1。
- 8. 黑色直线,表示变量的赋值。
- 8. 蓝色直线,表示指向封闭环境空间。
- 9. 橙色直线,表示函数调用过程。
- 10. 程序运行时,产生了函数环境空间的内存地址,包括0x000000000e0db220,0x000000000e0e8cf0, 0x000000000e0db178。
上图中结构,用R代码描述。
# 在当前环境定义变量x
> x<-5
# 在当前环境定义fun1
> fun1<-function(){
+ # 打印fun1环境空间
+ print("fun1")
+ print(environment())
+
+ # 在fun1函数环境中,定义变量x
+ x<-1
+ function() {
+ # 打印匿名环境空间
+ print("funX")
+ print(environment())
+ # 从一级父环境空间中,找到变量x
+ x+15
+ }
+ }
# 在当前环境定义fun2
> fun2<-function(){
+ # 打印fun2环境空间
+ print("fun2")
+ print(environment())
+
+ # 在fun2函数环境中,定义变量x
+ x<-2
+ fun1() #调用函数fun1
+ }
# 在当前环境空间中,调用函数fun2,绑定到f2
> f2<-fun2()
[1] "fun2"
<environment: 0x000000000e0db220>
[1] "fun1"
<environment: 0x000000000e0e8cf0>
# 在当前环境空间中,调用匿名函数,并绑定到fx
> fx<-f2()
[1] "funX"
<environment: 0x000000000e0db178>
# 输出fx的结果
> fx
[1] 16
最后,通过完整的例子,我们清楚了R语言环境空间的基本结构和调用关系。接下来,我们就可以利用环境空间的特性来做一些实际的开发任务了。