R中的类来自python背景

She*_*ock 18 oop r

我是一名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"
Run Code Online (Sandbox Code Playgroud)

我找不到任何能告诉我如何在R中做这么简单的事情.如果有人能告诉我一种方法,我会很感激吗?

非常感谢

Ric*_*ton 31

R实际上有很多不同的面向对象的实现.三个原生的(如@jthetzel所述)是S3,S4和Reference Classes.

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

Reference Classes旨在更接近其他编程语言的类.他们或多或少取代了S4类,它们做同样的事情,但是以更笨拙的方式.

所述R.oo包提供了另一种系统,以及包装允许面向原型的编程,它是像轻质OOP.OOP包中有第六个系统,但现在已经不存在了.最新的R6软件包"是R内置参考类的一种更简单,更快,更轻的替代品".

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

python类最容易转换为引用类.它们是相对较新的(直到John Chambers完成他们的书),最好的参考是?ReferenceClasses页面.这是一个让你入门的例子.

要定义一个类,请调用setRefClass.第一个参数是类的名称,按照惯例,它应该与您为其分配结果的变量相同.您还需要将lists 传递给参数"fields"和"methods".

有一些怪癖.

  • 如果您不想指定字段应具有的变量类型,请将"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)
    }
  )
)
Run Code Online (Sandbox Code Playgroud)

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

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

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

进一步阅读:高级R 的OO现场指南章节


Cas*_*unn 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
Run Code Online (Sandbox Code Playgroud)

创建此类后,创建和使用实例(在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
Run Code Online (Sandbox Code Playgroud)

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

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

In [9]: c.diameter
Out[9]: 4
Run Code Online (Sandbox Code Playgroud)

与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
    }
)
Run Code Online (Sandbox Code Playgroud)

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

> a <- new("Circle")
> radius(a) <- 2
> a <- calc_diameter(a)
> radius(a)
[1] 2
> diameter(a)
[1] 4
Run Code Online (Sandbox Code Playgroud)

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)diameter(a)调用执行,我们返回这些值定义的方法.

您也可以像在python中的对象一样直接访问R中对象的数据属性.但是,不使用点符号,而是使用@符号:

> a@radius
[1] 2
> a@diameter
[1] 4
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 虽然此链接可能会回答这个问题,但最好在此处包含答案的基本部分并提供参考链接.如果链接的页面发生更改,则仅链接的答案可能会无效. (2认同)