由于我对R语言还比较陌生,所以不了解S3方法和对象是什么。我发现有S3和S4对象系统,并且一些人建议尽可能使用S3而不是S4(请参见Google的R风格指南http://google-styleguide.googlecode.com/svn/trunk/google-r-style.html)。然而,我不知道S3方法/对象的确切定义。
更新:截至2019年,Google的R风格指南超链接已移至此处。
?S3
或?UseMethod
来找到,但简而言之:print
、predict
和summary
方法。glm
的返回值具有类别glm
)print
),然后是一个点,然后是类名(例如:print.glm
)print
)进行一些准备工作才能使其工作,但如果您只是想符合现有方法名称,您不需要这样做(如果需要,请参见我之前提到的帮助文件)。predict(myfit, type="class")
比键入predict.mykindoffit(myfit, type="class")
更为方便。为了让你开始使用S3,看一看median
函数的代码。在命令提示符下键入median
,可以发现它的主体有一行代码,即
UseMethod("median")
这意味着它是一个S3方法。换句话说,您可以为不同的S3类别拥有不同的median
函数。要列出所有可能的中位数方法,请键入
methods(median) #actually not that interesting.
在这种情况下,只有一个方法可用,默认情况下该方法被调用。您可以通过输入以下代码来查看它的代码median.default
一个更有趣的例子是print
函数,该函数有许多不同的方法。
methods(print) #very exciting
注意,有些方法的名称旁边带有*
,这意味着它们被隐藏在某个包的命名空间中。使用find
找出它们所在的包。例如:
find("acf") #it's in the stats package
stats:::print.acf
根据http://adv-r.had.co.nz/OO-essentials.html,R语言的三个面向对象系统在类和方法定义方面有所不同:
S3采用了一种称为泛型函数面向对象编程的风格。这与大多数编程语言(如Java、C++和C#)实现的消息传递面向对象不同。在消息传递中,将消息(方法)发送到对象,然后对象确定要调用哪个函数。通常,这个对象在方法调用中有一个特殊的外观,通常出现在方法/消息名称之前,例如canvas.drawRect("blue")。S3不同,虽然仍然通过方法进行计算,但是一种特殊类型的函数称为泛型函数决定要调用哪个方法,例如drawRect(canvas, "blue")。S3是一个非常休闲的系统,它没有类的正式定义。
S4与S3类似,但更加正式化。有两个主要区别:S4具有正式类定义,描述每个类的表示和继承,并具有用于定义泛型和方法的特殊辅助函数。S4还具有多重分派,这意味着泛型函数可以根据任意数量参数的类来选择方法,而不仅仅是一个。
引用类(简称RC)与S3和S4非常不同。RC实现了消息传递面向对象,因此方法属于类而不是函数。$用于分隔对象和方法,因此方法调用看起来像canvas$drawRect("blue")。RC对象也是可变的:它们不使用R语言通常的复制-修改语义,而是原地修改。这使得它们更难推理,但允许它们解决使用S3或S4难以解决的问题。
此外还有一个不太符合面向对象规范但很重要的系统:
- 基本类型是其他OO系统底层的C级别类型。虽然基本类型主要使用C代码进行操作,但了解它们很重要,因为它们为其他OO系统提供了构建块。
我最初来到这个问题是想知道这些名称的来源。根据维基百科的这篇文章,R语言的名称来源于S编程语言的一个版本。其他答案中描述的方法分派方案来自于S并且根据版本进行了标记。
试一试
methods(residuals)
其中包括“residuals.lm”和“residuals.glm”。也就是说,当您拟合线性模型m并输入residuals(m)
时,将调用residuals.lm。当您拟合广义线性模型时,将调用residuals.glm。
这有点像C++对象模型被颠倒了过来。在C++中,您定义一个基类,其中包含虚函数,派生类重写这些虚函数。
在R中,您定义一个虚拟(也称为通用)函数,然后决定哪些类将覆盖此函数(即定义方法)。请注意,执行此操作的类不需要派生自一个共同的超类。
我不会认为普遍更喜欢S3而不是S4。 S4具有更多正式化(=更多打字),这可能对某些应用程序来说太多了。但是,S4类可以像在C ++中定义类或结构体一样定义。例如,您可以指定某个类的对象由字符串和两个数字组成:
setClass("myClass", representation(label = "character", x = "numeric", y = "numeric"))
使用该类的对象调用的方法可以依赖于对象具有这些成员。这与S3类非常不同,S3类只是一堆元素的列表。
使用S3和S4,您通过fun(object, args)
调用成员函数,而不是object$fun(args)
。如果您正在寻找类似后者的东西,请查看proto包。
Hadley定义了以下内容来区分两种不同的面向对象编程方法:
函数式面向对象编程:方法(可调用的代码片段)属于通用函数(不要与Java / C# 通用方法混淆)。将方法视为位于全局查找表中。运行时系统会根据函数名称和传递给该函数的一个或多个参数的类型(或对象类)来查找要执行的方法(这称为“方法分派”)。从语法上讲,方法调用可能看起来像普通的函数调用:myfunc(object, arg1, arg2)
。如果语言支持,此调用将导致运行时查找与对(“myfunc”,typeof(object))或可能的(“myfunc”,typeof(object),typeof(arg1),typeof(arg2))相关联的方法。在R的S3中,通用函数的完整名称给出了(函数名,类)对。例如:mean.Date
是计算日期平均值的方法。尝试使用methods(“mean”)
列出具有函数名称mean
的通用方法。函数式面向对象编程方法可以在OO先驱Smalltalk,Common Lisp Object System和Julia中找到。Hadley指出:“与R相比,Julia的实现已经完全发展并且非常高效。”
封装的面向对象编程:方法属于对象或类,方法调用通常看起来像object.method(arg1, arg2)
。这被称为封装,因为对象封装了数据(字段)和行为(方法)。将方法视为附加到对象或对象类描述的查找表中。运行时根据方法名称和可能的一个或多个参数类型查找方法。这是"C++、Java、C#"等"流行"OO语言中采用的方法。
在两种情况下,如果支持继承(可能是),运行时可以向上遍历类层次结构,直到找到与调用查找键匹配的位置。
library(sloop) # formerly, "pryr"
otype(mtcars)
#> [1] "S3"
library(R6)
进行安装)。self
、private
、super
访问实例),也有成员函数(分配给字段的函数,但不是方法,只是函数)还有一些其他的包,比如 R.oo(类似于RC),proto(基于原型,类似JavaScript)和Mutatr。然而,“Advanced R”指出:
除了广泛使用的R6外,这些系统主要是理论上的兴趣。它们确实有其优点,但很少有R用户知道和理解它们,因此对于其他人阅读和贡献你的代码是困难的。
确保您也阅读了“Advanced R, 2nd edition”中权衡的章节。
median
是一个S3方法,而print
不是S3(也许是S4)? - Ankit Agrawalmethod(print)
,我看到了一个print.zoo*
但是尝试使用find("zoo")
查找它时,它会返回character(0)
而不是包名。 - Ankit Agrawal