用户定义的 S3 组通用函数如何在 R 中工作?
How do user defined S3 Group Generic Functions work in R?
我正在阅读 Advanced R by Hadley Wickham and I'm confused at section 13.7.3 Group Generics。
我对措辞有点困惑,“......你不能定义你自己的通用组......为你的 class 定义一个通用组......”但我认为这部分的意思是也就是说,如果我定义组泛型 Math.MyClass
,那么 Math
组泛型(abs
、sign
等)中的所有函数都将被 MyClass
覆盖对象。
这可以通过以下 运行 确认:
my_class <- structure(.Data = -1, class = "MyClass")
my_class
# [1] -1
# attr(,"class")
# [1] "MyClass"
abs(my_class)
# [1] 1
# attr(,"class")
# [1] "MyClass"
Math.MyClass <- function(x) { x }
abs(my_class)
# [1] -1
# attr(,"class")
# [1] "MyClass"
我知道这遵循 special naming scheme generic.class
但为什么 .Data
的值在 abs(my_class)
中受到影响?
当我创建变量 my_class
时,我设置了参数 .Data = -1
,-1 的 class 是 numeric
,这不应该改变:
class(unclass(my_class))
# [1] "numeric"
my_numeric <- unclass(my_class)
class(my_numeric)
# [1] "numeric"
abs(my_numeric)
# [1] 1
那为什么abs(my_class)
在我定义Math.MyClass
前后打印出相同的结果(1)?
如果我将泛型组定义为 Math.MyClass <- function(x) {NextMethod()}
,那么在定义 Math.MyClass
之前和之后我确实会收到相同的结果,但是那么拥有泛型组有什么意义呢?
而且,为什么我在定义 Math.matrix
之前和之后得到相同的 abs(my_matrix)
答案,当我 运行 以下内容时:
my_matrix <- matrix(data = -1:-10, ncol = 5) + 0.0
class(my_matrix)
# [1] "matrix"
class(my_matrix[1,1])
# [1] "numeric"
my_matrix
# [,1] [,2] [,3] [,4] [,5]
# [1,] -1 -3 -5 -7 -9
# [2,] -2 -4 -6 -8 -10
abs(my_matrix)
# [,1] [,2] [,3] [,4] [,5]
# [1,] 1 3 5 7 9
# [2,] 2 4 6 8 10
Math.matrix <- function(x) { x }
abs(my_matrix)
# [,1] [,2] [,3] [,4] [,5]
# [1,] 1 3 5 7 9
# [2,] 2 4 6 8 10
当我 运行 以下内容时:
your_class <- structure(.Data = list(-1), class = "YourClass")
your_class
# [[1]]
# [1] -1
#
# attr(,"class")
# [1] "YourClass"
abs(your_class)
# Error in abs(your_class) : non-numeric argument to mathematical function
class(unclass(your_class))
# [1] "list"
your_list <- list(-1)
class(your_list)
# [1] "list"
abs(your_list)
# Error in abs(your_list) : non-numeric argument to mathematical function
很明显,.Data
的 class
确实很重要(最初无论如何),因为 abs(your_class)
和 abs(your_list)
都会导致相同的错误。
为了让事情更具挑战性,我发现一旦 运行 rm(Math.MyClass)
:
MyClass
个对象,一切都会恢复正常
my_class
# [1] -1
# attr(,"class")
# [1] "MyClass"
abs(my_class)
# [1] -1
# attr(,"class")
# [1] "MyClass"
rm(Math.MyClass)
abs(my_class)
# [1] 1
# attr(,"class")
# [1] "MyClass"
有人可以更完整地解释什么是组泛型吗(为什么组泛型存在/它们完成什么/它们与 R 对象的父子关系是什么/为什么某些对象中的 data
参数会受到影响当组泛型被定义而其他组没有定义时 / 等等)?
如果您觉得用 Python 示例更容易解释,我在 Python 中的 OOP 经验比在 R 中的经验更多。非常感谢任何帮助!
组泛型允许您更改特定数据类型的一组函数的行为。最好的解释方法是看一些例子。如果你 运行 methods("Math")
你会看到哪些 class 定义了这个函数。
在 Math.Date
的情况下,您会看到
function (x, ...)
stop(gettextf("%s not defined for \"Date\" objects", .Generic),
domain = NA)
所有这些只是告诉您所有这些函数都没有为 Date 对象定义。例如
abs(as.Date("2020-01-01"))
# Error in Math.Date(as.Date("2020-01-01")) :
# abs not defined for "Date" objects
通过在组级别设置此行为,无需为 Math 组中的所有函数编写特殊版本的代码来告诉您它们未针对 class 定义,因为它们不是数字"以这种方式”。但是,即使 trunc()
在该列表中并且您可能希望得到一个错误,但它确实有效
trunc(as.Date("2020-01-01"))
[1] "2020-01-01"
那是因为定义了 trunc.Date
。
function (x, ...)
round(x - 0.4999999)
因此,组泛型的特殊之处在于您可以为常用 math-y 函数定义默认的“后备”行为。但是,如果您想更改该行为,您仍然可以为特定功能提供 class 特定实现。
请注意,数学组中列出的所有函数都调用了相同的 Math.MyClass
。该函数有一个变量可以知道实际调用了哪个函数。该变量称为 .Generic
并在 ?UseMethod
帮助页面上进行了讨论。例如
Math.MyClass<- function(x) { paste(.Generic, x) }
abs(my_class)
# [1] "abs -1"
trunc(my_class)
# [1] "trunc -1"
exp(my_class)
# [1] "exp -1"
这很有可能表明您不希望在 .Generic
上不进行调度就直接在其中放置任何转换。有关确实对某些函数类型进行分派的函数示例,请查看 Math.difftime
function (x, ...)
{
switch(.Generic, abs = , sign = , floor = , ceiling = , trunc = ,
round = , signif = {
units <- attr(x, "units")
.difftime(NextMethod(), units)
}, stop(gettextf("'%s' not defined for \"difftime\" objects",
.Generic), domain = NA))
}
您可以看到对于特定的函数子集,它会为“正常”实现分派,否则会抛出错误。
所以当你定义
Math.MyClass <- function(x) { x }
基本上,您告诉 R 您将为 class 的对象处理对 abs()
的调用。当你在你的实现中返回 x
不变时,你基本上只是返回了同一个对象,什么也没做。当您不定义 Math.MyClass
时,R 将通过“正常”步骤来确定如何调用该函数。由于您没有提供自定义“身份”函数,因此它会退回到默认的数字行为。
至于为什么matrix
行为没有改变,那是因为my_matrix
的class是隐式确定的。如果转储对象,您会看到
dput(my_matrix)
# structure(
# c(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10),
# .Dim = c(2L, 5L))
请注意,它不会像您的 my_class
对象那样将 class 存储在对象本身中
dput(my_class)
# structure(-1, class = "MyClass")
对于隐式 classes,调度发生的方式有点不同,它的调度更像是额外的 class 不存在。基于 class 的分派检查对象上的 class
属性。请注意,这会有所不同
my_matrix2 <- structure(my_matrix, class="matrix")
# abs(my_matrix2)
# [,1] [,2] [,3] [,4] [,5]
# [1,] -1 -3 -5 -7 -9
# [2,] -2 -4 -6 -8 -10
# attr(,"class")
# [1] "matrix"
你可以看到在这种情况下 Math.matrix
被调用并且没有任何改变。
我正在阅读 Advanced R by Hadley Wickham and I'm confused at section 13.7.3 Group Generics。
我对措辞有点困惑,“......你不能定义你自己的通用组......为你的 class 定义一个通用组......”但我认为这部分的意思是也就是说,如果我定义组泛型 Math.MyClass
,那么 Math
组泛型(abs
、sign
等)中的所有函数都将被 MyClass
覆盖对象。
这可以通过以下 运行 确认:
my_class <- structure(.Data = -1, class = "MyClass")
my_class
# [1] -1
# attr(,"class")
# [1] "MyClass"
abs(my_class)
# [1] 1
# attr(,"class")
# [1] "MyClass"
Math.MyClass <- function(x) { x }
abs(my_class)
# [1] -1
# attr(,"class")
# [1] "MyClass"
我知道这遵循 special naming scheme generic.class
但为什么 .Data
的值在 abs(my_class)
中受到影响?
当我创建变量 my_class
时,我设置了参数 .Data = -1
,-1 的 class 是 numeric
,这不应该改变:
class(unclass(my_class))
# [1] "numeric"
my_numeric <- unclass(my_class)
class(my_numeric)
# [1] "numeric"
abs(my_numeric)
# [1] 1
那为什么abs(my_class)
在我定义Math.MyClass
前后打印出相同的结果(1)?
如果我将泛型组定义为 Math.MyClass <- function(x) {NextMethod()}
,那么在定义 Math.MyClass
之前和之后我确实会收到相同的结果,但是那么拥有泛型组有什么意义呢?
而且,为什么我在定义 Math.matrix
之前和之后得到相同的 abs(my_matrix)
答案,当我 运行 以下内容时:
my_matrix <- matrix(data = -1:-10, ncol = 5) + 0.0
class(my_matrix)
# [1] "matrix"
class(my_matrix[1,1])
# [1] "numeric"
my_matrix
# [,1] [,2] [,3] [,4] [,5]
# [1,] -1 -3 -5 -7 -9
# [2,] -2 -4 -6 -8 -10
abs(my_matrix)
# [,1] [,2] [,3] [,4] [,5]
# [1,] 1 3 5 7 9
# [2,] 2 4 6 8 10
Math.matrix <- function(x) { x }
abs(my_matrix)
# [,1] [,2] [,3] [,4] [,5]
# [1,] 1 3 5 7 9
# [2,] 2 4 6 8 10
当我 运行 以下内容时:
your_class <- structure(.Data = list(-1), class = "YourClass")
your_class
# [[1]]
# [1] -1
#
# attr(,"class")
# [1] "YourClass"
abs(your_class)
# Error in abs(your_class) : non-numeric argument to mathematical function
class(unclass(your_class))
# [1] "list"
your_list <- list(-1)
class(your_list)
# [1] "list"
abs(your_list)
# Error in abs(your_list) : non-numeric argument to mathematical function
很明显,.Data
的 class
确实很重要(最初无论如何),因为 abs(your_class)
和 abs(your_list)
都会导致相同的错误。
为了让事情更具挑战性,我发现一旦 运行 rm(Math.MyClass)
:
MyClass
个对象,一切都会恢复正常
my_class
# [1] -1
# attr(,"class")
# [1] "MyClass"
abs(my_class)
# [1] -1
# attr(,"class")
# [1] "MyClass"
rm(Math.MyClass)
abs(my_class)
# [1] 1
# attr(,"class")
# [1] "MyClass"
有人可以更完整地解释什么是组泛型吗(为什么组泛型存在/它们完成什么/它们与 R 对象的父子关系是什么/为什么某些对象中的 data
参数会受到影响当组泛型被定义而其他组没有定义时 / 等等)?
如果您觉得用 Python 示例更容易解释,我在 Python 中的 OOP 经验比在 R 中的经验更多。非常感谢任何帮助!
组泛型允许您更改特定数据类型的一组函数的行为。最好的解释方法是看一些例子。如果你 运行 methods("Math")
你会看到哪些 class 定义了这个函数。
在 Math.Date
的情况下,您会看到
function (x, ...)
stop(gettextf("%s not defined for \"Date\" objects", .Generic),
domain = NA)
所有这些只是告诉您所有这些函数都没有为 Date 对象定义。例如
abs(as.Date("2020-01-01"))
# Error in Math.Date(as.Date("2020-01-01")) :
# abs not defined for "Date" objects
通过在组级别设置此行为,无需为 Math 组中的所有函数编写特殊版本的代码来告诉您它们未针对 class 定义,因为它们不是数字"以这种方式”。但是,即使 trunc()
在该列表中并且您可能希望得到一个错误,但它确实有效
trunc(as.Date("2020-01-01"))
[1] "2020-01-01"
那是因为定义了 trunc.Date
。
function (x, ...)
round(x - 0.4999999)
因此,组泛型的特殊之处在于您可以为常用 math-y 函数定义默认的“后备”行为。但是,如果您想更改该行为,您仍然可以为特定功能提供 class 特定实现。
请注意,数学组中列出的所有函数都调用了相同的 Math.MyClass
。该函数有一个变量可以知道实际调用了哪个函数。该变量称为 .Generic
并在 ?UseMethod
帮助页面上进行了讨论。例如
Math.MyClass<- function(x) { paste(.Generic, x) }
abs(my_class)
# [1] "abs -1"
trunc(my_class)
# [1] "trunc -1"
exp(my_class)
# [1] "exp -1"
这很有可能表明您不希望在 .Generic
上不进行调度就直接在其中放置任何转换。有关确实对某些函数类型进行分派的函数示例,请查看 Math.difftime
function (x, ...)
{
switch(.Generic, abs = , sign = , floor = , ceiling = , trunc = ,
round = , signif = {
units <- attr(x, "units")
.difftime(NextMethod(), units)
}, stop(gettextf("'%s' not defined for \"difftime\" objects",
.Generic), domain = NA))
}
您可以看到对于特定的函数子集,它会为“正常”实现分派,否则会抛出错误。
所以当你定义
Math.MyClass <- function(x) { x }
基本上,您告诉 R 您将为 class 的对象处理对 abs()
的调用。当你在你的实现中返回 x
不变时,你基本上只是返回了同一个对象,什么也没做。当您不定义 Math.MyClass
时,R 将通过“正常”步骤来确定如何调用该函数。由于您没有提供自定义“身份”函数,因此它会退回到默认的数字行为。
至于为什么matrix
行为没有改变,那是因为my_matrix
的class是隐式确定的。如果转储对象,您会看到
dput(my_matrix)
# structure(
# c(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10),
# .Dim = c(2L, 5L))
请注意,它不会像您的 my_class
对象那样将 class 存储在对象本身中
dput(my_class)
# structure(-1, class = "MyClass")
对于隐式 classes,调度发生的方式有点不同,它的调度更像是额外的 class 不存在。基于 class 的分派检查对象上的 class
属性。请注意,这会有所不同
my_matrix2 <- structure(my_matrix, class="matrix")
# abs(my_matrix2)
# [,1] [,2] [,3] [,4] [,5]
# [1,] -1 -3 -5 -7 -9
# [2,] -2 -4 -6 -8 -10
# attr(,"class")
# [1] "matrix"
你可以看到在这种情况下 Math.matrix
被调用并且没有任何改变。