1. 程式人生 > >解密R語言函式的環境空間

解密R語言函式的環境空間

前言

本文接上一篇文章揭開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語言環境空間的基本結構和呼叫關係。接下來,我們就可以利用環境空間的特性來做一些實際的開發任務了。