我是一名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中找不到任何东西能告诉我如何做这样简单的事情。如果有人能告诉我一种方法,我会很感激?
非常感谢
R实际上有很多不同的面向对象实现。三个原生类(如@jthetzel所述)是S3、S4和参考类。
S3是一个轻量级系统,允许您基于第一个参数的类重载函数。
引用类被设计为更类似于其他编程语言中的类。它们或多或少地取代了S4类,后者做同样的事情,但更笨拙。
R.oo包提供了另一个系统,proto包允许面向原型的编程,这就像轻量级OOP。OOP包中有第六个系统,但现在已经失效。最近的R6包"是R内置参考类的更简单、更快、重量更轻的替代品"。
对于新项目,您通常只想使用S3和Reference类(或者可能使用R6)。
python类最容易转换为引用类。它们相对较新,(直到约翰·钱伯斯完成了关于它们的书)最好的参考资料是?ReferenceClasses
页。下面是一个让你开始学习的例子。
要定义一个类,可以调用setRefClass
。第一个参数是类的名称,按照惯例,它应该与分配结果的变量相同。您还需要将list
传递给参数"字段"one_answers"方法"。
有一些怪癖。
- 如果不想指定字段应该具有的变量类型,请将"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字段指南章节
我最近写了一篇python类和R S4类的比较,可以在以下位置找到:
http://practicalcomputing.org/node/80
R和python中的类在声明方式、使用方式和工作方式方面都有很大不同。
根据mbinette在评论中的请求,以下是帖子的全文(减去大多数超链接,因为我只有两个权限):
对于任何使用python、C++、java或其他常见面向对象语言编程的人来说,R中的面向对象编程可能会非常令人困惑。遵循书中罗塞塔代码示例的精神,在这里,我将在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()
函数使用__init__()
定义的构造函数创建类Circle
的新实例。我们使用.setradius()
方法设置半径值,使用.calc_diameter()
方法根据半径计算直径并更新类实例中的直径值。然后,我们使用我们构建的方法来获得半径和直径的值。当然,我们也可以直接访问半径和直径值,使用我们用来调用函数的相同点符号:
In [8]: c.radius
Out[8]: 2
In [9]: c.diameter
Out[9]: 4
与C++、java和许多其他常见语言一样,方法和数据变量都是类的属性。此外,方法具有对数据属性的直接读写访问权限。在这种情况下,.calc_diameter()
方法将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的副本,将半径值更新为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)
调用执行我们为返回这些值而定义的方法。
您也可以直接访问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类需要更多的类型,而且其中大部分都是多余的(例如,在上面的示例中,每个方法名称必须指定三次)。因为R方法只能通过复制整个对象来访问数据属性,所以一旦对象变大,即使是简单的操作也会对性能造成很大影响。对于修改对象的方法来说,这个问题更为复杂,因为数据必须在输入时复制一次,然后在输出时复制一一次。这些问题可能是最近用于数值分析的python工具(如panda)迅速流行的原因之一。也就是说,R仍然是一个强大的工具,它非常适合许多常见的问题,丰富的R库生态系统对于许多分析来说是必不可少的。
我来自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)
第二种方法是使用initialize方法。
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
第五种方法是创建一个类方法来实例化类本身,这对于验证输入非常有用,即使缺少参数:
.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