为什么在R中初始化空对象时应该使用{}?

15

看起来一些程序员正在使用:

a = {}
a$foo = 1
a$bar = 2

使用a = list(foo = 1, bar = 2)有什么好处?

为什么应该使用{}?这个表达式只返回NULL,所以一个NULL赋值会做同样的事情,不是吗?


11
这很可能是从其他编程语言的使用中延续下来的。那位GitHub用户是一名通晓多种语言的人,有时候旧习惯难以改变。 - hrbrmstr
我只在SymbolHound上找到了6个使用<- {}的SO实例。 (http://symbolhound.com/?q=&l=&e=%3C-+%7B%7D&n=&u=) - smci
2个回答

15

你的第一个查询

为什么应该使用{},这个表达式只返回NULL,所以NULL赋值会产生相同的效果,是吗?

是的,a <- NULL会产生相同的效果。使用{}很可能是个人风格。


NULL

NULL可能是最多才多艺且令人困惑的R对象。根据R语言定义中对NULL的描述:

当需要指示或说明对象不存在时,使用它。不要将其与长度为零的向量或列表混淆。

NULL对象没有类型和可修改属性。在R中只有一个NULL对象,所有实例都引用它。要测试是否为NULL,请使用is.null。您无法对NULL设置属性。

严格来说,NULL就是NULL。而且它是唯一一个使is.null返回TRUE的东西。但是,根据?NULL:

值为NULL的对象可以通过替换运算符进行更改,并将强制转换为右侧的类型。

因此,虽然它与具有合法模式的长度为0的向量不完全相同(R中并不允许所有模式用于向量;请阅读?mode获取完整的模式列表,以及?vector获取向量的合法内容),但这种灵活的强制转换通常使它的行为类似于长度为0的向量:

## examples of atomic mode
integer(0)  ## vector(mode = "integer", length = 0)
numeric(0)  ## vector(mode = "numeric", length = 0)
character(0)  ## vector(mode = "character", length = 0)
logical(0)  ## vector(mode = "logical", length = 0)

## "list" mode
list()  ## vector(mode = "list", length = 0)

## "expression" mode
expression()  ## vector(mode = "expression", length = 0)

您可以进行向量拼接:

c(NULL, 0L)  ## c(integer(0), 0L)
c(NULL, expression(1+2))  ## c(expression(), expression(1+2))
c(NULL, list(foo = 1))  ## c(list(), list(foo = 1))

你可以像在问题中所做的那样增加一个向量:
a <- NULL; a[1] <- 1; a[2] <- 2
## a <- numeric(0); a[1] <- 1; a[2] <- 2

a <- NULL; a[1] <- TRUE; a[2] <- FALSE
## a <- logical(0); a[1] <- TRUE; a[2] <- FALSE

a <- NULL; a$foo <- 1; a$bar <- 2
## a <- list(); a$foo <- 1; a$bar <- 2

a <- NULL; a[1] <- expression(1+1); a[2] <- expression(2+2)
## a <- expression(); a[1] <- expression(1+1); a[2] <- expression(2+2)

使用{}生成NULL与使用expression()类似。虽然它们不完全相同,但在稍后进行操作时的运行时强制转换使它们几乎无法区分。例如,在扩展列表时,以下任何一种方法都可以:

a <- NULL; a$foo <- 1; a$bar <- 2
a <- numeric(0); a$foo <- 1; a$bar <- 2  ## there is a warning
a <- character(0); a$foo <- 1; a$bar <- 2  ## there is a warning
a <- expression(); a$foo <- 1; a$bar <- 2
a <- list(); a$foo <- 1; a$bar <- 2

对于一个长度为0且具有原子模式的向量,在运行时强制转换时会产生警告(因为从“原子”到“递归”的变化太大):

#Warning message:
#In a$foo <- 1 : Coercing LHS to a list

我们没有收到关于表达式设置的警告,因为来自?expression

作为模式‘"expression"’的对象是一个列表...

嗯,它不是通常意义上的“列表”;它类似于列表的是一个抽象语法树。

你的第二个查询

使用 a = list(foo = 1, bar = 2) 有什么好处吗?

没有优势。你应该已经在其他地方读到过,在R中增长对象是一种不好的做法。在谷歌上随机搜索可以得到:增长对象和循环内存预分配

如果你知道向量的长度以及每个元素的值,直接创建它,如 a = list(foo = 1, bar = 2)

如果你知道向量的长度,但其元素的值需要计算(例如通过循环),则设置一个向量并填充,如 a <- vector("list", 2); a[[1]] <- 1; a[[2]] <- 2; names(a) <- c("foo", "bar")


回复Tjebo

我实际上查了一下?mode,但它没有列出可能的模式。它指向?typeof,然后指向在src/main/util.c中列出的结构TypeTable中列出的可能值。我甚至找不到这个文件,也找不到文件夹(OSX)。你知道在哪里可以找到吗?

它指的是R分发的源代码,这是CRAN上的一个“.tar.gz”文件。另一种方法是查看https://github.com/wch/r-source。无论哪种方式,这就是表格:

TypeTable[] = {
    { "NULL",       NILSXP     },  /* real types */
    { "symbol",     SYMSXP     },
    { "pairlist",   LISTSXP    },
    { "closure",    CLOSXP     },
    { "environment",    ENVSXP     },
    { "promise",    PROMSXP    },
    { "language",   LANGSXP    },
    { "special",    SPECIALSXP },
    { "builtin",    BUILTINSXP },
    { "char",       CHARSXP    },
    { "logical",    LGLSXP     },
    { "integer",    INTSXP     },
    { "double",     REALSXP    }, /*-  "real", for R <= 0.61.x */
    { "complex",    CPLXSXP    },
    { "character",  STRSXP     },
    { "...",        DOTSXP     },
    { "any",        ANYSXP     },
    { "expression", EXPRSXP    },
    { "list",       VECSXP     },
    { "externalptr",    EXTPTRSXP  },
    { "bytecode",   BCODESXP   },
    { "weakref",    WEAKREFSXP },
    { "raw",        RAWSXP },
    { "S4",     S4SXP },
    /* aliases : */
    { "numeric",    REALSXP    },
    { "name",       SYMSXP     },

    { (char *)NULL, -1     }
};

7
根据 R 的括号和圆括号文档(键入“{'”以阅读),花括号将返回其中评估的最后一个表达式。
在这种情况下,a <- {} 本质上“返回”空对象,因此等同于 a <- NULL,它建立了一个可以被视为列表的空变量。
顺便说一句,这就是为什么可以编写 R 函数,其中函数的输出只需将返回的变量名称作为函数的最后一个语句。例如:
function(x) {
    y <- x * 2
    return(y)
}

等同于:

function(x) {
    y <- x * 2
    y
}

甚至可以这样做:
function(x) {
    y <- x * 2
}

函数的最后一行是一个赋值,它抑制了在控制台中打印结果,但如果将其保存到变量中,则该函数肯定会返回预期的值。


1
谢谢你的回答。我知道{}返回最后一个被评估的表达式,但那不是我的问题。我的问题是,为什么有人更喜欢写{}而不是NULL。没有人会写a = {1}而不是a = 1。只是因为节省了三个字符吗?我猜想这样做可能会有一些缺点,比如对于直接赋值NULL的情况,解析器速度更快。 - petres

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接