【问题标题】:How do user defined S3 Group Generic Functions work in R?用户定义的 S3 组通用函数如何在 R 中工作?
【发布时间】:2020-11-30 17:07:54
【问题描述】:

我正在阅读Advanced R by Hadley Wickham,但我对13.7.3 Group Generics部分感到困惑。

我对措辞有点困惑,“......你不能定义你自己的组泛型......为你的班级定义一个单一的组泛型......”但我认为本节的意思是说,如果我定义通用组Math.MyClass 然后Math 组通用(abssign 等)中的所有函数将被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的类是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 &lt;- 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

很明显,.Dataclass 确实很重要(最初无论如何),因为 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 参数会受到影响?已定义而其他未定义 /etc)?

如果您觉得用 Python 示例解释起来更容易,我在 Python 中的 OOP 方面比在 R 中更有经验。非常感谢任何帮助!

【问题讨论】:

    标签: r r-s3


    【解决方案1】:

    组泛型允许您更改特定数据类型的一组函数的行为。解释这一点的最好方法是看一些例子。如果你运行methods("Math"),你会看到哪些类定义了这个函数。

    如果是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
    

    通过在组级别设置此行为,无需编写数学组中所有函数的特殊版本来告诉您它们不是为该类定义的,因为它们不是“以这种方式”的数字。但是,即使 trunc() 在该列表中并且您可能希望得到一个错误,这实际上是有效的

    trunc(as.Date("2020-01-01"))
    [1] "2020-01-01"
    

    那是因为trunc.Date 已定义。

    function (x, ...) 
    round(x - 0.4999999)
    

    因此,组泛型的特殊部分是您可以为常见的数学函数定义默认的“回退”行为。但是,如果您想改变这种行为,您仍然可以为特定功能提供类特定的实现。

    请注意,对于 Math 组中列出的所有函数,都会调用相同的 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,您将为该类的对象处理对 abs() 的调用。当您在实现中原封不动地返回 x 时,您基本上只是返回了相同的对象并且什么也没做。当您没有定义Math.MyClass 时,R 会通过“正常”步骤来确定如何调用该函数。由于您没有提供自定义的“身份”函数,因此它回退到默认的数字行为。

    至于matrix 的行为为什么没有改变,那是因为my_matrix 的类是隐式确定的。如果你确实转储了对象,你会看到

    dput(my_matrix)
    # structure(
    #     c(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10), 
    #     .Dim = c(2L, 5L))
    

    请注意,它不会像 my_class 对象那样将类存储在对象本身中

    dput(my_class)
    # structure(-1, class = "MyClass")
    

    对于隐式类,调度发生的方式有点不同,它的调度更像是额外的类不存在。基于类的调度检查对象上的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 被调用并且没有任何改变。

    【讨论】:

      猜你喜欢
      • 2022-01-10
      • 1970-01-01
      • 2019-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多