22

我是一名 Python 程序员,这是我使用 R 的第一天。

我正在尝试使用构造函数和三个方法编写一个类,但我很挣扎。

在 python 中,这很容易:

 class MyClass:
      def __init__(self):
           self.variableA = 1
           self.variableB = 2

      def hello(self):
           return "Hello"

      def goodbye(self):
           return "Goodbye"

      def ohDear(self):
           return "Have no clue"

我找不到任何能告诉我如何在 R 中做如此简单的事情的东西。如果有人能告诉我一种方法,我将不胜感激?

非常感谢

4

3 回答 3

37

R 实际上有很多不同的面向对象的实现。三个原生的(如@jthetzel 提到的)是 S3、S4 和参考类。

S3 是一个轻量级系统,允许您根据第一个参数的类重载函数。

参考类被设计成更接近于其他编程语言的类。它们或多或少地取代了 S4 类,后者做同样的事情,但以更笨重的方式。

R.oo包提供另一个系统,proto包允许面向原型的编程,类似于轻量级 OOP。OOP包中有第六个系统,但现在已经不存在了。更新的R6“是 R 的内置引用类的更简单、更快、更轻量级的替代方案”

对于新项目,您通常只想使用 S3 和 Reference 类(或者可能是 R6)。

python 类最容易转换为引用类。它们相对较新,(直到约翰钱伯斯完成他的书)最好的参考是?ReferenceClasses页面。这是一个让您入门的示例。

要定义一个类,你调用setRefClass. 第一个参数是类的名称,按照惯例,这应该与您将结果分配给的变量相同。您还需要将lists 传递给参数“字段”和“方法”。

有一些怪癖。

  • 如果您不想指定字段应具有的变量类型,请将“ANY”作为字段列表中的值传递。
  • 任何构造函数逻辑都需要在一个名为initialize.
  • 如果方法的第一行是字符串,则将其解释为该方法的文档。
  • 在方法内部,如果要分配给字段,请使用全局分配 ( <<-)。

这将创建一个类生成器:

MyClass <- setRefClass(
  "MyClass",
  fields = list(
    x = "ANY",
    y = "numeric",
    z = "character"
  ),
  methods = list(
    initialize = function(x = NULL, y = 1:10, z = letters)
    {
      "This method is called when you create an instance of the class."
      x <<- x
      y <<- y
      z <<- z
      print("You initialized MyClass!")
    },
    hello = function()
    {
      "This method returns the string 'hello'."
      "hello"
    },
    doubleY = function()
    {
      2 * y
    },
    printInput = function(input)
    {
      if(missing(input)) stop("You must provide some input.")
      print(input)
    }
  )
)

然后通过调用生成器对象来创建类的实例。

obj1 <- MyClass$new()
obj1$hello()
obj1$doubleY()

obj2 <- MyClass$new(x = TRUE, z = "ZZZ")
obj2$printInput("I'm printing a line!")

延伸阅读:Advanced R 的OO 领域指南章节。

于 2012-07-19T13:57:12.987 回答
11

我最近写了一个 python 类和 R S4 类的比较,可以在以下位置找到:

http://practicalcomputing.org/node/80

R 和 python 中的类在声明方式、使用方式和工作方式上都非常不同。

根据 mbinette 在评论中的要求,这是帖子的全文(减去大多数超链接,因为我只有两个权限):

对于使用 python、C++、java 或其他常见的面向对象语言进行编程的任何人来说,R 中的面向对象编程可能会相当混乱。本着书中 Rosetta 代码示例的精神,在这里我将在 python 中创建和使用类的代码与在 R 中创建和使用类的代码进行比较。

混淆的第一层是 R 有几个不同的面向对象编程系统 - S3、S4 和 R5。面临的第一个决定是为您的项目选择哪一个。S3 出现的时间最长,并且被广泛使用。它的功能在某些关键方面受到限制,但程序员在如何编写类方面具有相当大的灵活性。S4 是一个较新的系统,解决了 S3 的一些限制。编码有点复杂和僵化,但最终使用起来更强大。通常,人们在处理已经具有 S3 对象的现有代码时使用 S3,而在从头开始实施新代码时使用 S4。例如,许多较新的生物导体包都是用 S4 编写的。Hadley Wickham对 S3、S4 和 R5 有很好的总结,以及 R 的其他方面,这是一个很好的地方,可以让自己更多地了解 R 中的面向对象编程。

这里我重点介绍S4系统。

下面是python中一个简单的Circle类的定义。它有一个__init__()用于在创建新实例时设置值的构造方法,一些用于设置值的方法,一些用于获取值的方法,以及一个通过根据半径计算直径来修改类实例的方法。

class Circle:

    ## Contents
    radius = None
    diameter = None

    ## Methods
    # Constructor for creating new instances
    def __init__(self, r):
        self.radius = r

    # Value setting methods
    def setradius(self, r):
        self.radius = r

    def setdiameter(self, d):
        self.diameter = d

    # Value getting methods
    def getradius(self):
        return(self.radius)

    def getdiameter(self):
        return(self.diameter)

    # Method that alters a value
    def calc_diameter(self):
        self.diameter = 2 * self.radius

创建此类后,创建和使用实例(在 ipython 中)如下所示:

In [3]: c = Circle()

In [4]: c.setradius(2)

In [5]: c.calc_diameter()

In [6]: c.getradius()
Out[6]: 2

In [7]: c.getdiameter()
Out[7]: 4

该函数使用定义的构造Circle()函数创建类的新实例。我们使用设置半径值的方法,以及根据半径计算直径并更新类实例中的直径值的方法。然后我们使用我们构建的方法来获取半径和直径的值。我们当然也可以直接访问半径和直径值,使用与调用函数相同的点表示法:Circle__init__().setradius().calc_diameter()

In [8]: c.radius
Out[8]: 2

In [9]: c.diameter
Out[9]: 4

与 C++、java 和许多其他通用语言一样,方法和数据变量都是类的属性。此外,方法具有对数据属性的直接读写访问权限。在这种情况下,该.calc_diameter()方法将直径值替换为新值,而无需更改有关类实例的任何其他内容。

现在对于 R 中的 S4 对象,它们非常非常不同。这是 R 中类似的 Circle 类:

setClass(
    Class = "Circle", 
    representation = representation(
        radius = "numeric", 
        diameter = "numeric"
    ),
)

# Value setting methods
# Note that the second argument to a function that is defined with setReplaceMethod() must be named value
setGeneric("radius<-", function(self, value) standardGeneric("radius<-"))
setReplaceMethod("radius", 
    "Circle", 
    function(self, value) {
        self@radius <- value
        self
    }
)

setGeneric("diameter<-", function(self, value) standardGeneric("diameter<-"))
setReplaceMethod("diameter", 
    "Circle", 
    function(self, value) {
        self@diameter <- value
        self
    }
)

# Value getting methods
setGeneric("radius", function(self) standardGeneric("radius"))
setMethod("radius", 
    signature(self = "Circle"), 
    function(self) {
        self@radius
    }
)

setGeneric("diameter", function(self) standardGeneric("diameter"))
setMethod("diameter", 
    signature(self = "Circle"), 
    function(self) {
        self@diameter
    }
)


# Method that calculates one value from another
setGeneric("calc_diameter", function(self) { standardGeneric("calc_diameter")})
setMethod("calc_diameter", 
    signature(self = "Circle"), 
    function(self) {
        self@diameter <- self@radius * 2
        self
    }
)

创建此类后,创建和使用实例(在 R 交互式控制台中)如下所示:

> a <- new("Circle")
> radius(a) <- 2
> a <- calc_diameter(a)
> radius(a)
[1] 2
> diameter(a)
[1] 4

new("Circle")调用创建了Circle该类的一个新实例,我们将其分配给了一个名为 的变量a。该radius(a)<- 2行创建了对象 a 的副本,将 radius 的值更新为 2,然后将 a 指向新更新的对象。这是用radius<-上面定义的方法完成的。

我们定义calc_diameter()Circle类的方法,但请注意,我们不要将其称为类的属性。也就是说,我们不使用类似a.calc_diameter(). 相反,我们calc_diameter()像调用任何其他独立函数一样调用,并将对象作为第一个参数传递给方法。

此外,我们不只是调用calc_diameter(a),我们将输出分配回a。这是因为 R 中的对象作为值而不是引用传递给函数。该函数获取对象的副本,而不是原始对象。然后在函数中对该副本进行操作,如果您想要返回修改后的对象,您必须做两件事。首先,对象必须在函数的最后一行执行(因此self方法定义中的行是孤立的)。在 R 中,这就像调用return(). 其次,您必须在调用该方法时将更新后的值复制回我们的对象变量。这就是为什么整行是a <- calc_diameter(a).

radius(a)anddiameter(a)调用执行我们为返回这些值而定义的方法。

您还可以直接访问 R 中对象的数据属性,就像您可以访问 python 中的对象一样。但是,您不使用点表示法,而是使用@表示法:

> a@radius
[1] 2
> a@diameter
[1] 4

在 R 中,数据属性被称为“槽”。该@语法使您可以访问这些数据属性。但是方法呢?与 python 不同,R 中的方法不是对象的属性,它们被定义setMethod()为作用于特定对象。该方法所作用的类由signature参数确定。但是,可以有多个具有相同名称的方法,每个方法都作用于不同的类。这是因为被调用的方法不仅取决于方法的名称,还取决于参数的类型。一个熟悉的例子是方法plot()。对用户来说,看起来只有一个plot()函数,但实际上有许多plot()方法都特定于特定的类。plot().

这会到达setGeneric()类定义中的行。如果您使用已存在的名称(例如plot())定义新方法,则不需要它。这是因为setMethod()定义了现有方法的新版本。新版本采用一组不同的数据类型,然后是同名的现有版本。但是,如果要使用新名称定义函数,则首先必须声明该函数。setGeneric()处理这个声明,创建本质上是一个占位符,然后您会立即覆盖它。

python 和 R 中的类之间的差异不仅仅是表面上的,在底层发生了非常不同的事情,并且在每种语言中类以不同的方式使用。不过,在 R 中创建和使用类时,有一些事情特别令人沮丧。在 R 中创建 S4 类需要更多的输入,而且大部分都是多余的(例如,在上面的示例中,每个方法名称必须指定 3 次)。因为 R 方法只能通过复制整个对象来访问数据属性,所以一旦对象变大,即使是简单的操作也会对性能造成很大影响。对于修改对象的方法,这个问题更加复杂,因为数据必须在输入时复制一次,然后在输出时复制一次。这些问题可能促成了最近用于数值分析的 python 工具(如 pandas)的迅速普及。也就是说,R 仍然是一个强大的工具,它非常适合解决许多常见问题,并且 R 库的丰富生态系统对于许多分析来说是必不可少的。

于 2012-11-25T06:17:28.327 回答
1

我来自 Python 世界,一开始很难用 R 类来思考。最后,我做到了。我认为。

我能够在 R 中将Python 和 Java 类转换为S4 。

创建此包rODE的特定目的是帮助初学者学习S4课程。该软件包是关于求解常微分方程的,并且包含有关 S4 类的所有内容。

希望能帮助到你。链接下方:

https://github.com/f0nzie/rODE

该软件包也在 CRAN 中。

现在,关于答案。在 R 中有很多方法可以做你想做的事。这是第一种方法,使用S4类和原型来初始化变量 A 和 B 的值。

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    ),
    prototype = prototype(
        A = 1,
        B = 2
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})


# instantiate a class
mc <- new("MyClass")

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

第二种方法是使用初始化方法。

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    .Object@A <- 1
    .Object@B <- 2
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})


# instantiate a class
mc <- new("MyClass")

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

mc@A       # get value on slot A
mc@B       # get value on slot B

第三种方法是使用构造函数并使用构造函数初始化类外部的类变量:

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})


# constructor function
MyClass <- function() {
    myclass <- new("MyClass")
    myclass@A <- 1            # assignment
    myclass@B <- 2
    return(myclass)           # return the class initialized
}

# instantiate a class
mc <- MyClass()

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

mc@A       # get value on slot A
mc@B       # get value on slot B

这仍然有改进的空间,因为我们不应该在类之外处理插槽的原始名称。封装,还记得吗?这是使用setReplaceMethod的第四种更好的方法:

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))
setGeneric("getA", function(object, ..., value) standardGeneric("getA"))
setGeneric("getB", function(object, ..., value) standardGeneric("getB"))
setGeneric("setA<-", function(object, ..., value) standardGeneric("setA<-"))
setGeneric("setB<-", function(object, ..., value) standardGeneric("setB<-"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})

setMethod("getA", "MyClass", function(object, ...) {
    return(object@A)
})

setMethod("getB", "MyClass", function(object, ...) {
    return(object@B)
})

setReplaceMethod("setA", "MyClass", function(object, ..., value) {
   object@A <- value
   object
})

setReplaceMethod("setB", "MyClass", function(object, ..., value) {
   object@B <- value
   object
})

# constructor function
MyClass <- function() {
    myclass <- new("MyClass")
    return(myclass)           # return the class initialized
}

# instantiate a class
mc <- MyClass()

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

setA(mc) <- 1
setB(mc) <- 2

getA(mc)       # get value on slot A
getB(mc)       # get value on slot B

第 5 种方法是创建一个类方法来实例化类本身,这对于验证输入很有用,即使是缺少参数:

.MyClass <- setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))
setGeneric("getA", function(object, ..., value) standardGeneric("getA"))
setGeneric("getB", function(object, ..., value) standardGeneric("getB"))
setGeneric("setA<-", function(object, ..., value) standardGeneric("setA<-"))
setGeneric("setB<-", function(object, ..., value) standardGeneric("setB<-"))
setGeneric("MyClass", function(A, B, ...) standardGeneric("MyClass"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})

setMethod("getA", "MyClass", function(object, ...) {
    return(object@A)
})

setMethod("getB", "MyClass", function(object, ...) {
    return(object@B)
})

setReplaceMethod("setA", "MyClass", function(object, ..., value) {
   object@A <- value
   object
})

setReplaceMethod("setB", "MyClass", function(object, ..., value) {
   object@B <- value
   object
})

setMethod("MyClass", signature(A="numeric", B="numeric"), function(A, B, ...) {
    myclass <- .MyClass()
    myclass@A <- A
    myclass@B <- B
    return(myclass)
})

# instantiate the class with values
mc <- MyClass(A = 1, B = 2)

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

getA(mc)       # get value on slot A
getB(mc)       # get value on slot B
于 2017-07-20T14:22:26.260 回答