揭開R語言中環境空間的神秘面紗
前言
環境空間(environment)對於大部分的R使用者來說,都是比較陌生的。雖然我們不了解它的運行原理,但也不影響我們使用R語言。環境空間是R語言中關於計算機方面的底層設計,主要用於R語言是環境載入器。通過環境空間,封裝了載入器的運行過程,讓使用者在不知道底層細節的情況下,可以任意載入使用到的第三方的R語言程序包。
本文將揭開R語言中環境空間的神秘面紗。
目錄
- R語言的環境空間
- 環境空間的特徵
- 環境空間的訪問
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>
通過找父環境空間,我們看到整個環境空間的層次結構,如圖所示。
通過層次結構圖,又可以發現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$xNULL> 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: Cant find y> e1$y[1] 99# 在e1空間查看y變數> where("y",e1)<environment: 0x2545db0>
本文介紹了R語言中,環境空間的定義、結構和一些簡單的使用,當然這並不是環境空間的全部內容。下一篇文章,將繼續介紹函數環境空間的定義和使用,解密R語言函數的環境空間
作者介紹:
張丹,R語言中文社區專欄特邀作者,《R的極客理想》系列圖書作者,民生銀行大數據中心數據分析師,前況客創始人兼CTO。
10年IT編程背景,精通R ,Java, Nodejs 編程,獲得10項SUN及IBM技術認證。豐富的互聯網應用開發架構經驗,金融大數據專家。個人博客 粉絲日誌, Alexa全球排名70k。
著有《R的極客理想-工具篇》、《R的極客理想-高級開發篇》,合著《數據實踐之美》,新書《R的極客理想-量化投資篇》(即將出版)。
《R的極客理想-工具篇》京東購買快速通道:《數據分析技術叢書:R的極客理想·工具篇》(張丹 )【摘要 書評 試讀】- 京東圖書
《R的極客理想-高級開發篇》京東購買快速通道:《R的極客理想 高級開發篇》(張丹)【摘要 書評 試讀】- 京東圖書
《數據實踐之美》京東購買快速通道:《數據實踐之美:31位大數據專家的方法、技術與思想》(天善智能)【摘要 書評 試讀】- 京東圖書
大家也可以加小編微信:tswenqu(備註:知乎),進R語言中文社區 交流群,可以跟各位老師互相交流
官方公眾號:R語言中文社區 (ID:R_shequ) 歡迎關注,持續連載。
推薦閱讀:
※數據學習之路—每周好文分享(第二期)
※【數據處理】dplyr包(下)
※防止爬蟲採集數據的框架或者策略有哪些?
※Kaggle案例:2016美國大選