为什么使用assign是不好的?

41

这篇文章 (R中的惰性求值 - assign 受到影响吗?) 涵盖了一些共同点,但我不确定它是否回答了我的问题。

相当长一段时间以来,我已经停止使用 assign,而是转而使用 apply 系列,尽管纯粹出于类似于这种情况的优雅原因:

names.foo <- letters
values.foo <- LETTERS
for (i in 1:length(names.foo))
  assign(names.foo[i], paste("This is: ", values.foo[i]))

可以被替换为:

foo <- lapply(X=values.foo, FUN=function (k) paste("This is :", k))
names(foo) <- names.foo

这也是(http://cran.r-project.org/doc/FAQ/R-FAQ.html#How-can-I-turn-a-string-into-a-variable_003f) R-faq提到应该避免使用的原因。

现在,我知道assign通常不受欢迎。但是还有其他我不知道的原因吗? 我怀疑它可能会影响作用域或惰性评估,但我不确定。如果有演示此类问题的示例代码,将非常好。

3个回答

38
实际上这两个操作是非常不同的。第一个给你26个不同的对象,而第二个只给你一个。第二个对象将更容易在分析中使用。所以我想我会说你已经展示了assign的主要缺点,即需要始终使用get来收集或汇总所有名称相似的单独对象,因为它们现在是"松散"在全局环境中的。试着想象一下你如何序列地处理那26个单独的对象。对于第二种策略,一个简单的lapply(foo, func)就足够了。
那个FAQ引用只是说使用赋值然后分配名称更容易,但并没有暗示它是"坏的"。我碰巧把它看作是"不太功能性的",因为你实际上没有返回一个被分配的值。这个效果看起来像是一个副作用(而在这种情况下,assign策略会产生26个单独的副作用)。看起来使用assign的人们都是来自具有全局变量的语言的人们,他们采用这种方式是为了避免接受"真正的R方法",即使用数据对象的函数式编程。他们真的应该学习使用列表,而不是在他们的工作空间里乱贴标签。
还有另一种可以使用的赋值范例:
 foo <- setNames(  paste0(letters,1:26),  LETTERS)

这将创建一个命名的原子向量,而不是一个命名的列表,但通过给 [ 分配名称仍然可以访问向量中的值。


10
我认为另一个重要点是在编写函数时。函数只能返回一个对象,因此列表成为一种方便的包装器,用于返回多个对象。如果没有列表,你需要将函数“分配”给父环境中的变量,即产生副作用。这样做会遭到严厉批评。 - flodel
3
apply函数的定义中是否有循环?常见的反应是使用apply系列中的函数。这不是向量化,而是隐藏循环。apply函数在其定义中具有for循环。"lapply函数隐藏了循环,但执行时间往往与显式的for循环大致相等。" R-Inferno Circle 4(过度向量化)- [链接](http://www.burns-stat.com/pages/Tutor/R_inferno.pdf) - marbel
1
没错,他并不完全反对使用apply函数。他说:当每次迭代是一个非平凡的任务时,请使用显式的for循环。但是,一个简单的循环可以更清晰、更简洁地使用apply函数表达。这个规则至少有一个例外。 - marbel
1
@MartínBel:有人在研究R地狱了! ;) - asb
1
@MartínBel:在运行时间和内存消耗方面的妥协是以块而不是逐个元素的方式工作。再想一想,如果您没有足够的内存来进行单个x的复制,我怀疑您的数据分析也不会有很大进展。 - cbeleites unhappy with SX
显示剩余7条评论

15
作为fortune(236)的来源,我想再加上几个例子(也请参见fortune(174))。
首先是一个小测验。考虑以下代码:
x <- 1
y <- some.function.that.uses.assign(rnorm(100))

运行上面的2行代码后,x的值是多少?

assign函数用于进行“远程操作”(参见http://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)或谷歌搜索)。这通常是难以找到错误的根源。

我认为assign的最大问题在于它往往会使人们偏离更好的选择。一个简单的例子是问题中的2组代码。使用lapply的解决方案更加优雅,应该得到推广,但是人们了解assign函数的事实导致他们选择使用循环选项。然后他们决定需要在循环中创建的每个对象上执行相同的操作(如果使用优雅的解决方案,这将只是另一个简单的lapplysapply),并采用更复杂的循环涉及getapply以及对paste的不美观调用。然后那些热衷于assign的人尝试做类似于:

curname <- paste('myvector[', i, ']')
assign(curname, i)

结果并不完全符合他们的预期,这导致了对R的抱怨(这就像因为我选择绕路走而抱怨我的邻居的房子太远一样公平),甚至更糟的是,涉及使用evalparse来使他们构造的字符串“工作”(这随后导致了fortune(106)fortune(181))。


8
我想指出assign应该与environment一起使用。
从这个角度来看,上面示例中的"坏"之处是使用了不太适当的数据结构(基本环境而不是listdata.framevector,...)。
旁注:对于environment$$<-运算符也可以工作,因此在许多情况下,显式的assignget也不是必须的。

5
通常人们使用assign的原因是为了得到一个构造的变量名,这在使用$<-时是无法实现的。因此我们还应该注意到,[[<-可以与环境一起“工作”,所以可以这样做:myEnv[[paste0("my", "Var", 1)]] <- value - IRTFM

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