• Posts tagged "空间"

Blog Archives

解密R语言函数的环境空间

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/

environments-function

前言

本文接上一篇文章揭开R语言中环境空间的神秘面纱,继续介绍R语言中函数的环境空间。

R语言的函数环境空间,具有动态性,可以让我们用更少的代码构建出更复杂的应用来。

目录

  1. R语言的函数环境空间
  2. 封闭环境
  3. 绑定环境
  4. 运行环境
  5. 调用环境
  6. 完整的环境操作

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. 完整的环境操作

接下来,把函数环境空间的操作放到一起,做一个完整环境的说明。

funenv

图片解释:

  • 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语言环境空间的基本结构和调用关系。接下来,我们就可以利用环境空间的特性来做一些实际的开发任务了。

转载请注明出处:
http://blog.fens.me/r-environments-function/

打赏作者

揭开R语言中环境空间的神秘面纱

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/

r-environments

前言

环境空间(environment)对于大部分的R使用者来说,都是比较陌生的。虽然我们不了解它的运行原理,但也不影响我们使用R语言。环境空间是R语言中关于计算机方面的底层设计,主要用于R语言是环境加载器。通过环境空间,封装了加载器的运行过程,让使用者在不知道底层细节的情况下,可以任意加载使用到的第三方的R语言程序包。

本文将揭开R语言中环境空间的神秘面纱。

目录

  1. R语言的环境空间
  2. 环境空间的特征
  3. 环境空间的访问

1 R语言的环境空间

在R语言中,不管是变量,对象,或者函数,都存在于R的环境空间中,R程序在运行时都自己的运行时空间。R语言的环境(environment)是由内核定义的一个数据结构,由一系列的、有层次关系的框架(frame)组成,每个环境对应一个框架,用来区别不同的运行时空间(scope)。

环境空间有一些特征,比如 每个环境空间要有唯一的名字;环境空间是引入类型的,非赋值类型;环境空间都有父环境空间,空环境是最顶层的环境空间,没有父空间;子环境空间会继承父环境空间的变量等。

本文的系统环境

  • Linux: Ubuntu Server 12.04.2 LTS 64bit
  • R: 3.0.1 x86_64-pc-linux-gnu

为了方便我们检查对象的类型,引入pryr包作为辅助工具。关于pryr包的介绍,请参考文章:撬动R内核的高级工具包pryr


# 加载pryr包
> library(pryr)

1.1 创建一个环境

查看new.env()函数的定义。


new.env(hash = TRUE, parent = parent.frame(), size = 29L)

参数列表:

  • hash 默认值是TRUE,使用Hash table的结构。
  • parent 指定要创建环境的父环境。
  • size 初始化的环境空间大小。

运行函数new.env(),创建一个新环境。


# 创建环境e1
> e1 <- new.env()

# 输出e1
> e1
<environment: 0x3d7eef0>

# 查看e1类型
> class(e1)
[1] "environment"

# otype查看e1类型,属于基本类型
> otype(e1)
[1] "primitive"

接下来,我们在e1环境中定义一个变量。


# 定义变量a
> e1$a <- 10

# 输出变量a
> e1$a
[1] 10

# 列出当前环境中的变量
> ls()
[1] "e1"

# 列出e1环境中的变量
> ls(e1)
[1] "a"

这时,我们看到了两个环境空间,当前环境空间和e1环境空间。e1做为一个变量在当前的环境中被定义,而变量a是在e1环境中被定义。

1.2 环境空间的层次结构

R语言的环境是一种有层次关系的结构,每个环境都有上一层环境,直到最顶层的空环境。R语言中有5种环境的定义 全局环境,内部环境,父环境,空环境 和 包环境。

  • 当前环境,即用户环境,是用户程序运行的环境空间。
  • 内部环境,构造出来的环境,可以是通过 new.env()函数显示创建的环境空间,也可以是匿名的环境空间。
  • 父环境,即上一层环境,环境空间的上一层。
  • 空环境,即顶层环境,没有父环境空间。
  • 包环境,包封装的环境空间。

# 当前环境
> environment()
<environment: R_GlobalEnv>

# 内部环境
> e1 <- new.env()
> e1
<environment: 0x3e28948>

# 父环境
> parent.env(e1)
<environment: R_GlobalEnv>

# 空环境
> emptyenv()
<environment: R_EmptyEnv>

# 包环境
> baseenv()
<environment: base>

可以用search() 函数查看当前环境中加载的R包。


# 查看环境空间
> search()
 [1] ".GlobalEnv"        "package:pryr"      "package:stats"
 [4] "package:graphics"  "package:grDevices" "package:utils"
 [7] "package:datasets"  "package:methods"   "Autoloads"
[10] "package:base"

# 当前的环境空间
> .GlobalEnv
<environment: R_GlobalEnv>
> parent.frame()
<environment: R_GlobalEnv>

查看父环境空间


# e1环境的父环境空间
> parent.env(e1)
<environment: R_GlobalEnv>

# 当前环境的父环境空间
> parent.env(environment())
<environment: package:pryr>
attr(,"name")
[1] "package:pryr"
attr(,"path")
[1] "/home/conan/R/x86_64-pc-linux-gnu-library/3.0/pryr"

# base包环境的父环境空间
> parent.env(baseenv())
<environment: R_EmptyEnv>

# 空环境的父环境空间,因没有父环境,所以出现错误
> parent.env(emptyenv())
Error in parent.env(emptyenv()) : the empty environment has no parent

既然环境空间是有层次关系的,那么我们打印这个层次结构,从自定义的e1环境到空环境。


# 递归打印父环境空间
> parent.call<-function(e){
+   print(e)
+   if(is.environment(e) & !identical(emptyenv(),e)){
+     parent.call(parent.env(e))
+   }
+ }

# 运行函数
> parent.call(e1)
<environment: 0x366bf18>
<environment: R_GlobalEnv>
<environment: package:pryr>
attr(,"name")
[1] "package:pryr"
attr(,"path")
[1] "/home/conan/R/x86_64-pc-linux-gnu-library/3.0/pryr"
<environment: package:stats>
attr(,"name")
[1] "package:stats"
attr(,"path")
[1] "/usr/lib/R/library/stats"
<environment: package:graphics>
attr(,"name")
[1] "package:graphics"
attr(,"path")
[1] "/usr/lib/R/library/graphics"
<environment: package:grDevices>
attr(,"name")
[1] "package:grDevices"
attr(,"path")
[1] "/usr/lib/R/library/grDevices"
<environment: package:utils>
attr(,"name")
[1] "package:utils"
attr(,"path")
[1] "/usr/lib/R/library/utils"
<environment: package:datasets>
attr(,"name")
[1] "package:datasets"
attr(,"path")
[1] "/usr/lib/R/library/datasets"
<environment: package:methods>
attr(,"name")
[1] "package:methods"
attr(,"path")
[1] "/usr/lib/R/library/methods"
<environment: 0x20cb5d0>
attr(,"name")
[1] "Autoloads"
<environment: base>
<environment: R_EmptyEnv>

通过找父环境空间,我们看到整个环境空间的层次结构,如图所示。

env-process

通过层次结构图,又可以发现R包的加载顺序。 最先加载的是base包,然后通过base::Autoloads()函数,分别加载6个基础包,上层的pryr包则是我手动加载的,最后以R_GlobalEnv环境为当前运行环境空间,内部环境空间是R_GlobalEnv环境的下层环境空间。

2. 环境空间的特征

上面中提到环境空间有一些特征,下面我们分别介绍一下。

2.1 每个环境空间中的对象名字要唯一

在当前环境空间中定义变量名x,并对x进行操作。


# 定义变量x
> x<-10;x
[1] 10

# 查看x地址
> address(x)
[1] "0x2874068"

# 对x改变赋值
> x<-11;x
[1] 11

# 查看x地址
> address(x)
[1] "0x28744c8"

这样我们可以看到,x变量在每次赋值的时候,内存地址都会发生改变,但是x的名字还是x。

在不同的环境空间中,再定义一个变量x。


# 创建环境空间e1
> e1<-new.env()

# 在e1中定义变量x
> e1$x<-20

# 输出x
> x;e1$x
[1] 12
[1] 20

在不同的环境空间中,可以有同名的变量名字。

2.2 环境空间变量的赋值

如果把e1环境空间变量,赋值给另一个变量f,再修改其环境内部变量,会是什么结果呢?


# 把e1赋值给f
> f <- e1

# 修改e1中a变量的值
> e1$a <- 1111

# 查看f环境空间的a值
> f$a
[1] 1111

# 比较f环境和e1环境,是相等的
> identical(f,e1)
[1] TRUE

# 查看e1和f的环境地址,是完全相同的
> e1
<environment: 0x3e28948>
> f
<environment: 0x3e28948>

所以,环境空间的赋值,是一种引入的传递,而不是新创建一个环境空间。

2.3 定义更上层的环境空间

空环境是最顶层的环境空间,然后是base包的环境空间,我们可以尝试创建一个靠近顶层的环境空间,让父环境空间是base包的环境空间。


# 创建e2环境,以base为父环境
> e2 <- new.env(parent = baseenv())
> e2
<environment: 0x37cab18>

# 查看e2环境的父环境列表
> parent.call(e2)
<environment: 0x37cab18>
<environment: base>
<environment: R_EmptyEnv>

这样e2环境空间就位于了环境空间中的第三层。

2.4 子环境空间会继承父环境空间的变量

在当前环境中,定义一个变量x, 子环境e1中,对x重新赋值。


# 在当前环境,定义变量x
> x<-1:5

# 新建环境空间e1
> e1 <- new.env()

# e1环境空间中定义变量x
> e1$x<-1

# 在e1环境空间中定义函数,并对父环境空间的x变量重新赋值
> e1$fun<-function(y){
+   print('e1::fun')
+   x<<-y
+ }

# 运行e1环境空间中的函数,将x赋值为50
> e1$fun(50)
[1] "e1::fun"

# 当前环境x变量被修改
> x
[1] 50

# e1环境x变量没有变化
> e1$x
[1] 1

这样我们就可以利用 <<- 赋值符号,来修改父环境中的变量。

3. 环境空间的访问

R语言中有一些辅助函数,可以帮助我们理解和使用环境空间。

  • new.env 创建一个环境空间
  • is.environment 判断是否是环境空间类型。
  • environment 查看函数的环境空间定义。
  • environmentName 查看环境空间名字。
  • env.profile 查看环境空间属性值。
  • ls 查看环境空间中的对象。
  • get 取出指定环境空间中的对象。
  • rm 删除环境空间中的对象。
  • assign 给环境空间中的变量赋值。
  • exists 查看指定环境空间中的对象是否存在。

接下来,我们进行环境空间的访问操作。


# 新建一个环境空间
> e1<-new.env()

# 判断e1是否是环境空间类型
> is.environment(e1)
[1] TRUE

# 查看当前环境空间
> environment()
<environment: R_GlobalEnv>

# 查看函数的环境空间
> environment(ls)
<environment: namespace:base>

# 查看环境空间的名字
> environmentName(baseenv())
[1] "base"
> environmentName(environment())
[1] "R_GlobalEnv"

# 查看e1环境空间的名字
> environmentName(e1)
[1] ""

# 设置e1的名字
> attr(e1,"name")<-"e1"
> environmentName(e1)
[1] "e1"

# 查看e1环境空间的属性值
> env.profile(e1)
$size
[1] 29

$nchains
[1] 1

$counts
 [1] 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

环境空间中的对象操作。


# 清空当前环境空间定义的所的对象
> rm(list=ls())

# 定义环境空间有,和3个变量x
> e1<-new.env()
> x<-1:5;y<-2:10
> e1$x<-10

# 查看当前环境中的变量
> ls()
[1] "e1" "x"  "y"

# 查看e1环境空间中的变量
> ls(e1)
[1] "x"

# 取当前环境空间的x值
> get("x")
[1] 1 2 3 4 5

# 取e1环境空间的x值
> get("x",envir=e1)
[1] 10

# 在e1环境空间中去y值,这个y值是从当前环境空间中继承的
> get("y",envir=e1)
[1]  2  3  4  5  6  7  8  9 10

# 禁止环境空间的继承,在e1环境空间中去y值,出错
> get("y",envir=e1,inherits=FALSE)
Error in get("y", envir = e1, inherits = FALSE) : object 'y' not found

# 给x重新赋值
> assign('x',77);x
[1] 77

# 给e1环境空间的x重新赋值
> assign('x',99,envir=e1);e1$x
[1] 99

# 在没有继承的情况下,给e1空间增加y变量
> assign('y',99,envir=e1,inherits=FALSE);
> y
[1]  2  3  4  5  6  7  8  9 10
> e1$y
[1] 99

# 删除e1环境空间的变量x,和当前环境空间的y
> rm(x,envir=e1)
> e1$x
NULL
> x
[1] 77

# 查看当前环境空间,和e1环境空间
>  ls()
[1] "e1" "x"
> ls(e1)
[1] "y"

# 查看x对象在当前环境空间是否存在
> exists('x')
[1] TRUE

# 查看x对象在e1环境空间是否存在
> exists('x',envir=e1)
[1] TRUE

# 查看x对象,在没有继承的情况下,在e1环境空间是否存在
> exists('x',envir=e1,inherits=FALSE)
[1] FALSE

另外,pryr包的where函数可以直接定位对象的环境空间。


# 查看mean函数定义的环境空间
> where(mean)
Error: is.character(name) is not TRUE
> where("mean")
<environment: base>

# 查看where函数定义的环境空间
> where("where")
<environment: package:pryr>
attr(,"name")
[1] "package:pryr"
attr(,"path")
[1] "/home/conan/R/x86_64-pc-linux-gnu-library/3.0/pryr"

# 查看x变量定义的环境空间
> where("x")
<environment: R_GlobalEnv>

# 查看y变量定义的环境空间,由于y变量定义在e1中,e1是当前空间的子空间,所以访问不到y变量
> where("y")
Error: Can't find y
> e1$y
[1] 99

# 在e1空间查看y变量
> where("y",e1)
<environment: 0x2545db0>

本文介绍了R语言中,环境空间的定义、结构和一些简单的使用,当然这并不是环境空间的全部内容。下一篇文章,将继续介绍函数环境空间的定义和使用,解密R语言函数的环境空间

转载请注明出处:
http://blog.fens.me/r-environments/

打赏作者