由于我对 R 相当陌生,因此我不知道 S3 方法和对象是什么。我发现有 S3 和 S4 对象系统,如果可能的话,有些人建议使用 S3 而不是 S4(请参阅http://google-styleguide.googlecode.com/svn/trunk/google-r-style 上的 Google 的 R 样式指南。 html )*。但是,我不知道 S3 方法/对象的确切定义。
更新:截至 2019 年,谷歌的 R 风格指南超链接现在在这里。
大多数相关信息可以通过查看?S3
or找到?UseMethod
,但简而言之:
S3 指的是一种方法分派的方案。如果您使用 R 一段时间,您会注意到有许多不同类型的对象的print
,predict
和summary
方法。
在 S3 中,这通过以下方式工作:
glm
有类glm
)print
)的方法,然后是一个点,然后是类名(例如
print.glm
:)print
( )。在旁观者的眼中,特别是对于您新创建的时髦模型配件包的用户来说,能够打字predict(myfit, type="class")
比predict.mykindoffit(myfit, type="class")
.
它还有很多内容,但这应该可以帮助您入门。这种基于对象的属性(类)调度方法的方式有很多缺点(C 纯粹主义者可能会因为害怕而彻夜难眠),但在很多情况下,它工作得很好。在当前版本的 R 中,已经实现了更新的方法(S4 和参考类),但大多数人仍然(仅)使用 S3。
为了让您开始使用 S3,请查看该median
函数的代码。在命令提示符下键入median
显示它的正文中有一行,即
UseMethod("median")
这意味着它是一种 S3 方法。换句话说,您可以median
为不同的 S3 类提供不同的功能。要列出所有可能的中值方法,请键入
methods(median) #actually not that interesting.
在这种情况下,只有一种方法,即默认方法,它可以为任何事情调用。您可以通过键入来查看该代码
median.default
一个更有趣的例子是print
函数,它有许多不同的方法。
methods(print) #very exciting
请注意,某些方法*
的名称旁边有 s。这意味着它们隐藏在某些包的名称空间中。用于find
找出它们所在的包。例如
find("acf") #it's in the stats package
stats:::print.acf
来自http://adv-r.had.co.nz/OO-essentials.html:
R 的三个 OO 系统在类和方法的定义方式上有所不同:
S3 实现了一种称为泛型函数 OO 的 OO 编程风格。这与大多数实现消息传递 OO 的编程语言(如 Java、C++ 和 C#)不同。通过消息传递,消息(方法)被发送到对象,对象决定调用哪个函数。通常,该对象在方法调用中具有特殊的外观,通常出现在方法/消息的名称之前:例如 canvas.drawRect("blue")。S3 不同。虽然计算仍然是通过方法执行的,但一种称为通用函数的特殊类型的函数决定调用哪个方法,例如 drawRect(canvas, "blue")。S3是一个非常随意的系统。它没有对类的正式定义。
S4 的工作方式与 S3 类似,但更正式。与 S3 有两个主要区别。S4 有正式的类定义,描述了每个类的表示和继承,并且有特殊的帮助函数来定义泛型和方法。S4 也有多重分派,这意味着泛型函数可以根据类的任意数量的参数来选择方法,而不仅仅是一个。
引用类,简称为 RC,与 S3 和 S4 有很大的不同。RC 实现了消息传递 OO,所以方法属于类,而不是函数。$ 用于分隔对象和方法,因此方法调用看起来像 canvas$drawRect("blue")。RC 对象也是可变的:它们不使用 R 通常的 copy-on-modify 语义,而是在原地修改。这使他们更难推理,但允许他们解决用 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 和 S4,您调用成员函数 byfun(object, args)
而不是 by object$fun(args)
。如果您正在寻找类似后者的东西,请查看 proto 包。
这里是根据Hadley Wickham(RStudio 首席科学家)的“Advanced R, 2nd edition” (CRC Press, 2019)对众多R 对象系统的更新快速概述,这里有一个网络表示,基于关于对象的章节- 面向编程。
2015 年的第一版在这里有一个网络表示,这里有关于 OO 的相应章节。
Hadley 定义了以下内容来区分 OO 编程的两种不同方法:
功能 OOP:方法(可调用的代码片段)属于泛型函数(不要与 Java/C#泛型方法混淆)。将这些方法视为位于全局查找表中。运行时系统根据函数的名称和传递给该函数的一个或多个参数的类型(或对象类)找到要执行的方法(这称为“方法分派”)。在语法方面,方法调用可能看起来像普通的函数调用:myfunc(object, arg1, arg2)
. 此调用将导致运行时查找与该对关联的方法(“myfunc”,typeof(object))或可能(“myfunc”,typeof(object),typeof(arg1),typeof(arg2))如果语言支持。在 R 的 S3 中,泛型函数的全名给出了(function-name, class)对。例如:mean.Date
是计算日期平均值的方法。尝试methods("mean")
用函数名列出泛型方法mean
。例如,在 OO 先驱Smalltalk、Common Lisp Object System和Julia中可以找到函数式 OOP 方法。Hadley 指出,“与 R 相比,Julia 的实现已经完全开发并且非常高效。”
封装的 OOP:方法属于对象或类,方法调用通常看起来像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。但是,“高级 R” 说:
除了广泛使用的 R6 之外,这些系统主要具有理论意义。它们确实有自己的优势,但很少有 R 用户知道和理解它们,因此其他人很难阅读并为您的代码做出贡献。
请务必阅读“Advanced R, 2nd edition”中关于权衡的章节。