这是一篇很有实力的干货,很多技术知识连我自己现在都还未掌握。
R中的每一个符号(symbol)都是定义在一个具体的环境中的。环境(environment)就是一个R对象(R中一切皆对象),其中包含给定上下文中的符号集合、与这些符号相关的对象,以及一个指向父环境的指针。
上面的定义非常的清晰,我们可以简单地把环境看做一个相对独立的空间,这个空间里包含很多物品(符号相关的对象),以及对它们的描述与表征(符号)。环境与其父环境之间通过指针相连,以沟通与外界联系。
当R解析一个符号时,会先查找当前的环境。如果在本地环境中没有匹配的符号,R就会逐级而上查找父环境中是否有能匹配的符号。
符号
符号这个概念也许刚接触R的话不会常听到,但你却实实在在每每刻刻都在用它。我记得学C语言时,对于变量赋值及其实现通常老师会举一个酒店或者旅馆的例子。如果说存储单元是一个个独立的房间,里面放了数据,那么符号就是门牌号了,指向了数据放在哪个房间。虽然R好像在概念上没有涉及寻址,但其内部必然封装了这一个过程。简单地广义地理解,R中的符号就是其他语言(C、Python等)变量、常量的泛化概念。
最简单的例子:
代码语言:javascript复制x <- 1
x
就是符号。想必看到这里大家应该都能明白了。
我们可以推迟表达式的求值,使符号不会立即被解析。
代码语言:javascript复制> x <- 1
> y <- 2
> z <- 3
> v <- quote(c(x,y,z))
> eval(v)
[1] 1 2 3
> v
c(x, y, z)
>
> x <- 5
> eval(v)
[1] 5 2 3
环境
环境也是对象,在内部R将符号映射到哈希表中。
下面展示操作环境对象的R函数。
函数 | 描述 |
---|---|
assign | 在envir环境中将名称x赋给value对象 |
get | 在envir环境中获得与名称x关联的对象 |
exists | 判断在环境envir中是否定义了名称x |
objects | 以向量的形式返回envir环境中定义的所有名称 |
remove | 从envir环境中移除罗列的对象 |
search | 以向量 形式返回所附着的包的名称 |
searchpaths | 以向量 形式返回所附着的包的路径 |
attach | 将数据对象添加到当前搜索路径 |
detach | 将数据对象从当前搜索路径中移除 |
emptyenv | 返回空环境对象。所有的环境链最终都会回到这个对象 |
parent.env | 返回env环境的父环境 |
baseenv | base包的环境 |
globalenv或.GlobalEnv | 返回用户工作空间的环境(也称为全局环境) |
environment | 为fun函数返回环境。没有参数时返回的就是当前环境 |
new.env | 返回一个新的环境对象 |
简单用一下其中的几个函数:
代码语言:javascript复制> x <- 1
> y <- 2
> z <- 3
> objects()
[1] "x" "y" "z"
> rm(x)
> objects()
[1] "y" "z"
函数的父环境就是创建该函数的环境。调用环境是使用该函数的环境。如果函数是在运行环境中创建的,那么父环境和调用环境是相同的。
调用堆栈
尽管函数的父环境并不一定是调用函数的环境,但函数总是可以访问到调用它的环境。与其他语言类似,R会维护一个调用环境栈。(堆栈的概念不懂的朋友可以自己百度查阅下资料)
下面列出了操作调用栈的函数。
函数 | 描述 |
---|---|
sys.call | 返回一个包含当前函数调用的语言对象 |
sys.frame | 返回调用环境 |
sys.nframe | 返回当前帧的编号(在堆栈中位置) |
sys.function | 返回当前计算的函数 |
sys.parent | 返回父帧的编号 |
sys.calls | 返回栈中所有帧的调用 |
sys.frames | 返回栈中所有的环境 |
sys.parents | 返回栈中每个帧的父帧 |
sys.on.exit | 返回当前帧on.exit所使用的表达式 |
sys.status | 返回一个列表,其中包括调用sys.calls,sys.parents和sys.frames的结果 |
parent.frame | 返回sys.frame(sys.parent(n))。换言之,返回父帧 |
异常
当输入了不正确的表达式时,R会给出错误的提示。例如
代码语言:javascript复制> 12 / 'hat'
Error in 12/"hat" : 二进列运算符中有非数值参数
有的时候,R会发出警告:
代码语言:javascript复制> if (c(TRUE, FALSE)) TRUE else FALSE
[1] TRUE
Warning message:
In if (c(TRUE, FALSE)) TRUE else FALSE :
条件的长度大于一,因此只能用其第一元素
和其他现代编程语言一样,R也可以在发生异常事件时发出异常信号,也可以捕获异常信号。异常处理和环境是密切相关的,在异常发生时,R解释器需要中止当前的函数,并向调用环境发出异常信号。
提示错误
用法非常简单,下面给出发错误和警告的小例子,我们在编写程序的时候照着用就可以了。
报错
代码语言:javascript复制> doWork <- function(filename){
if(file.exists(filename)){
read.delim(filename)
}else{
stop("Could not open the file: ", filename)
}
}
> doWork("file that doesn't exist")
Error in doWork("file that doesn't exist") :
Could not open the file: file that doesn't exist
报警告
代码语言:javascript复制> doWork <- function(filename){
if(file.exists(filename)){
"la la la"
}else{
warning("File does not exists: ", filename)
}
}
> doWork("another file that doesn't exist")
Warning message:
In doWork("another file that doesn't exist") :
File does not exists: another file that doesn't exist
如果只是想要告知用户某些信息,可以使用message
函数:
> doNothing <- function(x) {
message("This function does nothing.")
}
> doNothing("another input value")
This function does nothing.
捕获错误
用try
函数可以简单地实现这个目的。这个函数隐藏了R异常处理的复杂之处。下面用一个例子加以说明:
> res <- try({x <- 1}, silent=TRUE)
> res
[1] 1
> res <- try({open("file that doesn't exist")}, silent=TRUE)
> res
[1] "Error in UseMethod("open") : "open"没有适用于"character"目标对象的方法n"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in UseMethod("open"): "open"没有适用于"character"目标对象的方法>
try
带两个参数,expr
和silent
。第一个表示要尝试的R表达式。第二个参数表示是否要把错误信息显示到R控制台(或者标准错误输出stderr)。如果表达式运行发生了错误,try会返回一个try-error
类对象。
另一个更为强大的函数是tryCatch
。tryCatch
有三组参数:要尝试的表达式;一组用于不同条件的处理器;以及一个最终要计算的表达式。
格式:
代码语言:javascript复制tryCatch(expression, handler1, handler2, ..., finally=finalexpr)
R解释器首先会对expression求值。如果发生了某种情况,R就会选择相应的处理器。在表达式计算完后,就会计算finalexpr
。(如果计算了这个表达式,意味着任何处理器都不会被激活)
最后,推荐两篇很棒的相关文章:
- 揭开R语言中环境空间的神秘面纱
- 解密R语言函数的环境空间