R语言与面向对象的编程(3):R6类

2023-11-03


专注系列化、高质量的R语言教程

本号已支持快捷转载,无需白名单即可转载


本系列将介绍R语言中三个与面向对象的编程(Object-Oriented Programming,OOP)相关的工具包:protoR6和基础包methods。这是一个承上启下的系列,上承《自定义ggplot2绘图系统函数》系列,下启《基于mlr3工具包的机器学习》系列(该系列将在本系列之后推出)。这两个系列的编程风格都属于OOP,前者基于proto包的proto对象,后者基于R6包的R6类。

本篇介绍R6工具包,主要参考资料[1]
[https://r6.r-lib.org/articles/Introduction.html]。

  • 1 引言

  • 2 创建R6类和对象

    • 2.1 创建类

    • 2.2 创建对象

  • 3 私有属性和方法

  • 4 类的继承

  • 5 外部引用

  • 6 新增类的元素

  • 7 类的锁定和解锁

  • 8 克隆对象

1 引言

proto基于对象的编程不同,R6采用的是基于类的编程(class-based programming)。类(class)和对象(object)的关系是:对象是类的实例,类是对象的模板;定义对象之前需要先定义类[2]

R语言基础包中有S3类、S4类和reference class(R5类为其非正式称呼),R6是基于这个顺序命名的。

2 创建R6类和对象

2.1 创建类

创建R6类的函数是R6工具包中的R6Class(),它的语法结构如下:

R6Class(
  classname = NULL,
  public = list(),
  private = NULL,
  active = NULL,
  inherit = NULL,
  lock_objects = TRUE,
  class = TRUE,
  portable = TRUE,
  lock_class = FALSE,
  cloneable = TRUE,
  parent_env = parent.frame()
)

这里先看前两个参数:

  • classname:类的名称;

  • public:类的公用属性和方法。

由“类”到“对象”,需要使用基础包methods中的new()函数,它要求“类”里必须包含initialize方法。

如下是一个示例:

library(R6)
Person <- R6Class(
  classname = "Person",
  
  public = list(
    ## 属性
    name = NULL,
    age = NULL,
    
    ## 方法
    intro = function() {
      print(paste("Hello, I am", self$name, "and", 
                  self$age, "years old"))},
    initialize = function(name, age) {
      self$name <- name
      self$age <- age
      return(self$intro())}
  )
)

在上面的代码中,我们只使用了classnamepublic两个参数。前者无需多说。在public中,我们定义了两个属性:nameage;两个方法:introinitialize

要点如下:

  • public的数据结构形式为列表,元素(属性或方法)之间使用逗号隔开;

  • 可以使用self表示类本身,进而在方法中引用它的的属性和方法。

R6类的数据类型:

class(Person)
## [1] "R6ClassGenerator"

2.2 创建对象

从R6类创建对象的代码形式为object <- class$new(...),其中new()函数来自基础包methods,它的参数为类的initialize方法的参数。

根据类Person创建对象:

Tom <- Person$new("Tom", 20) 
## [1] "Hello, I am Tom and 20 years old"

Jane <- Person$new("Jane", 18) 
## [1] "Hello, I am Jane and 18 years old"  

Tom$age <- 30
Tom$intro()
## [1] "Hello, I am Tom and 30 years old"

3 私有属性和方法

public参数外,还可以使用R6Class()函数的private参数定义私有属性和方法。

要点如下:

  • private的数据结构形式为列表,元素(属性或方法)之间使用逗号隔开;

  • 私有属性和方法可以使用private$x的形式在其他方法中进行引用。

如下代码,我们把intro方法放到private参数中:

Person2 <- R6Class(
  classname = "Person2",
  public = list(
    name = NULL,
    age = NULL,
    initialize = function(name, age) {
      self$name <- name
      self$age <- age
      return(private$intro())}
  ), 
  
  ## 私有属性和方法
  private = list(
    intro = function() {
      print(paste("Hello, I am", self$name, "and", self$age, "years old"))} 
  )
)

根据类Person2创建对象:

Tom2 <- Person2$new("Tom", 20) 
## [1] "Hello, I am Tom and 20 years old"

属性和方法的私有部分与公用部分的区别是:前者不能使用object$x的形式进行访问。如下代码会报错:

Tom2$intro()
## Error: attempt to apply non-function

在RStudio中,当我们在对象后输入$时,自动联想中也不会出现私有部分,因此可以将一些作为过程的属性和方法放到私有部分。如下对比了TomTom2对象:

2cb9b23635d59cf183368dd6fcd5dd1a.png f0d5780c48acfd150d7d8cd8906237f0.png

4 类的继承

我们可以在一个类的基础上定义一个新类,这称为继承(inheritance);前者是后者的父类(super-class),后者是前者的子类(sub-class)。

子类除了继承或修改父类的属性和方法外,还可以定义新的属性和方法。下面代码中,我们在Person2的基础上修改intro方法形成Person3

library(lubridate)
Person3 <- R6Class(
  ## 继承
  inherit = Person2,
  
  private = list(
    ## 修改intro方法
    intro = function() {
      born = year(Sys.Date()) - self$age
      print(paste("Hello, I am", self$name, "and borned in", born))} 
  )
)

Tom3 <- Person3$new("Tom", 20) 
## [1] "Hello, I am Tom and borned in 2002"

在子类中可以以super$method(...)的形式调用父类的方法:

Person4 <- R6Class(
  inherit = Person2,
  
  public = list(
    initialize = function(name, age) {
      self$name <- name
      self$age <- age
      return(super$intro())}
  ),
  private = list(
    intro = function() {
      born = year(Sys.Date()) - self$age
      print(paste("Hello, I am", self$name, "and borned in", born))} 
  )
)

Tom4 <- Person4$new("Tom", 20) 
## [1] "Hello, I am Tom and 20 years old"

注意:调用的只是父类中方法的形式,参数值仍来自子类。

5 外部引用

如果把其他类的对象作为要定义类的属性,通常情况下应将赋值过程放在initialize方法中,否则该类的所有对象会共享这一属性值。

下面例子中,我们使用类SimpleClass的对象作为类Person5age属性。

不放在initialize方法中:

SimpleClass <- R6Class("SimpleClass",
                       public = list(x = NULL)
)

Person5 <- R6Class(
  classname = "Person5",
  public = list(
    name = NULL,
    ## 外部引用
    age = SimpleClass$new(),
    
    intro = function() {
      print(paste("Hello, I am", self$name, "and", 
                  self$age$x, "years old"))},
    initialize = function(name) {
      self$name <- name
      return(self$intro())}
  )
)

如下根据Person5创建两个对象,因为他们会共享age属性,所以年龄始终保持一致:

Tom5 <- Person5$new("Tom")
Jane5 <- Person5$new("Jane") 

Tom5$age$x <- 20
Tom5$intro()
## [1] "Hello, I am Tom and 20 years old"
Jane5$intro()
## [1] "Hello, I am Jane and 20 years old"

Tom5$age$x <- 30
Tom5$intro()
## [1] "Hello, I am Tom and 30 years old"
Jane5$intro()
## [1] "Hello, I am Jane and 30 years old"

放在initialize方法中:

Person6 <- R6Class(
  classname = "Person6",
  public = list(
    name = NULL,
    age = NULL,
    intro = function() {
      print(paste("Hello, I am", self$name, "and", 
                  self$age$x, "years old"))},
    
    initialize = function(name) {
      self$name <- name
      ## 在initialize方法内进行外部引用
      self$age <- SimpleClass$new()
      return(self$intro())}
  )
)

Tom6<- Person6$new("Tom")
Jane6 <- Person6$new("Jane") 
Tom6$age$x <- 20
Jane6$age$x <- 30

Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"
Jane6$intro()
## [1] "Hello, I am Jane and 30 years old"

6 新增类的元素

在类已被创建后,可以使用set()函数增加属性和方法。

如下,先生成类Person7,再在它的基础上新增一个公用方法born

Person7 <- R6Class(inherit = Person)
Person7$set("public", "born", function() year(Sys.Date()) - self$age)  

Tom7 <- Person7$new("Tom", 20) 
Tom7$born()
## [1] 2002

7 类的锁定和解锁

如果希望在类创建后,不允许对其进行修改,可以在创建时设置参数lock_class = TRUE。如下,此时再想新增元素就会报错:

Person8 <- R6Class(
  inherit = Person,
  lock_class = T
)

Person8$set("public", "born", function() year(Sys.Date()) - self$age)  
## Error in Person7$set("public", "born2", function() year(Sys.Date()) - : 
## Can't modify a locked R6 class.

锁定类后,可以使用unlock()函数对其进行解锁:

Person9 <- R6Class(
  inherit = Person,
  lock_class = T
)
## 解锁
Person9$unlock()

Person9$set("public", "born", function() year(Sys.Date()) - self$age)
Tom9 <- Person9$new("Tom", 20) 
Tom9$born()
## [1] 2002

8 克隆对象

这一部分对应proto对象的备份(详见proto对象)。在那篇推文中,我们已经知道使用赋值符号不能克隆环境对象,而只是给对象起了一个新名字:

Tom10 <- Tom
Tom$age <- 20
Tom10$intro()
## [1] "Hello, I am Tom and 20 years old"

Tom$age <- 30
Tom10$intro()
## [1] "Hello, I am Tom and 30 years old"

从本例可以看出,Tom10的内容会随着Tom的内容变化而变化;本质上,TomTom10是同一对象的两个名字。

克隆R6对象可以使用clone()函数:

Tom11 <- Tom$clone()
Tom11$age <- 40
Tom11$intro() 
## [1] "Hello, I am Tom and 40 years old"

Tom$intro()
## [1] "Hello, I am Tom and 30 years old"

如果想不允许对象被克隆,在创建类时可以设置参数cloneable = FALSE 。如果类中包含有外部引用,克隆时应设置参数deep = TRUE

## 未设置参数deep = T
Tom12 <- Tom6$clone()
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"
Tom12$age$x <- 20
Tom12$intro() 
## [1] "Hello, I am Tom and 20 years old"
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"

## 设置参数deep = T
Tom13 <- Tom6$clone(deep = T)
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"
Tom13$age$x <- 30
Tom13$intro() 
## [1] "Hello, I am Tom and 30 years old"
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"

更复杂的情况请参见参考资料[1]或“阅读原文”。

参考资料

[1]

Introduction to R6: https://r6.r-lib.org/articles/Introduction.html

[2]

类和对象: https://baike.baidu.com/item/%E7%B1%BB%E5%92%8C%E5%AF%B9%E8%B1%A1/1394902

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

R语言与面向对象的编程(3):R6类 的相关文章

随机推荐

  • Linux系统目录简介及常规操作命令

    文件类型 普通文件 regular file 就是一般存取的文件 由ls al显示出来的属性中 第一个属性为 例如 rwxrwxrwx 另外 依照文件的内容 又大致可以分为 1 纯文本文件 ASCII 这是Unix系统中最多的一种文件类型
  • 关系型数据库(mysql等)和非关系型数据库(Redis、ElasticSearch、HBase等)对比

    目录 一 关系型数据库 二 非关系型数据库 1 缓存型数据库 1 Redis 2 Memcached 2 文档型数据库 1 ElasticSearc 2 mongoDB 3 列式型数据库 1 HBase 2 Cassandra 一 关系型数
  • 玩无人机必备!PID调节经验

    Kp 比例系数 比例带 比例度 P 输入偏差信号变化的相对值与输出信号变化的相对值之比的百分数表示 比例系数的倒数 T 采样时间 Ti 积分时间 Td 微分时间 温度T P 20 60 Ti 180 600s Td 3 180s 压力P P
  • Linux nrm 运行失败,linux – npm安装失败

    我首先要说的是 我没有在终端或使用node js工作的经验 同事离开度假 我试图按照他离开的说明在我们的演示服务器上设置他的应用程序 我可以在本地运行所有内容 但是在安装socket io模块的服务器上遇到问题 安装了python 安装了n
  • The driver is automatically registered via the SPI -这是啥含义?

    jdbc Driver 被自动注册了 这里面牵扯到几件事 一一道来 1 何为SPI 它是如何把Driver加载进去的 SPI 全名 Service Provider Interface 是一种服务发现机制 它通过在ClassPath路径下的
  • 1094 谷歌的招聘 (20 分)

    2004 年 7 月 谷歌在硅谷的 101 号公路边竖立了一块巨大的广告牌 如下图 用于招聘 内容超级简单 就是一个以 com 结尾的网址 而前面的网址是一个 10 位素数 这个素数是自然常数 e 中最早出现的 10 位连续数字 能找出这个
  • 什么是动态住宅代理?

    随着网络的迅速发展 许多人对代理IP已经有了比较深刻的认识 并且广泛地运用到了各自的业务中 尤其在跨境的相关业务中表现尤其卓越 对于代理IP的类别 也需要根据自己的业务类型具体选择最合适的 那么今天就给大家具体介绍动态住宅IP代理这一类型
  • 蓝桥杯-训练-算法思维篇01

    理论概念篇 1 基础类 概览 评判 复杂度 思维 枚举 递归 二分 分治 动态规划 优先搜索 贪心算法 2 排序类 3 实现语言 语言 C C
  • flask模板

    代码块的使用 返回一个模板网页 文件目录结构 变量代码块使用 app py部分 index html网页部分 网页预览 控制代码块使用 index html网页部分 网页预览 返回一个模板网页 文件目录结构 flask static tem
  • Java版本+企业电子招投标系统源代码之电子招投标系统建设的重点和未来趋势

    计算机与网络技术的不断发展 推动了社会各行业信息化的步伐 时至今日 电子政务 电子商务已经非常普及 云计算 大数据 工业4 0 互联网 等发展理念也逐步深入人心 如何将传统行业与互联网科技有效结合起来 产生1 1 2的效果 不仅是每一个管理
  • [架构之路-182]-《软考-系统分析师》-19- 系统可靠性分析与设计 - 概览

    前言 可靠性工程是研究产品生命周期中故障的发生 发展规律 达到预防故障 消灭故 障 提高产品可用性的工程技术 信息系统的可靠性是指系统在满足一定条件的应用环境中能够正常工作的能力 可以按一般工程系统的可靠性标准进行定性评价 也可以通过平均无
  • 分享24个Python接单平台,有技术等于有收入!

    一 Python兼职种类 接私活刚学会python那会 就有认识的朋友介绍做一个网站的私活 当时接单赚了4K 后又自己接过开发网站后台接口 做数据处理等事情 都赚了一些 接私活指的是利用自己的技术 在业余时间搞定用户整块需求 对方在开始前预
  • Java自动化框架配置监听器生成测试报告邮件发送

    TestNG官网 https testng org doc documentation main html introduction TestNG博客 https www jianshu com p 8a729de618b6 一 接口自动化
  • JMETER接口测试_参数化和关联实现注册、登录和查询

    JMETER接口测试 参数化和关联 实现如下 1 注册接口 实现参数化 2 登录接口 关联用第一步中的用户和密码 3 博文查询接口 关联登录接口返回的token和userid 1 添加Thread Group 2 添加HTTP Reques
  • NameNode: Permission denied&无法启动Hadoop解决方法

    NameNode Permission denied publickey gssapi keyex gssapi with mic password 就是这个原因 这个问题的出现主要是因为没有给authorized keys授权 解决方法如
  • VMware虚拟机安装MacOS Big Sur

    之前完善了vm安装Windows系统的教程 今天给大家分享一个vm安装MacOS的教程 我们今天用macOS Big Sur版本来做教程演示 注 使用VMware安装MacOS哪怕配置给的高也会出现体验上的不佳 大家可以尽可能调高适当的配置
  • ElasticSearch 双数据中心建设在新网银行的实践

    本文公众号读者飞熊的投稿 本文主要讲述了ElasticSearch 双数据中心建设在新网银行的实践 作者简介 飞熊 目前就职于新网银行大数据中心 主要从事大数据实时计算和平台开发相关工作 对Flink Spark 以及ElasticSear
  • goland语法面试题

    文章目录 1 关于 switch 语句 下面说法正确的是 2 下面代码能编译通过吗 可以的话 输出什么 3 interface 是可以指向任意对象的 Any 类型 是否正确 4 下面的代码有什么问题 1 关于 switch 语句 下面说法正
  • Unity5热更新ILRuntime 使用 Protobuf3.0

    Unity5热更新ILRuntime 使用 Protobuf3 0 须知 1 pb3官方用到了C 很多的新语法 所以在unity主工程中直接撸码是不可以的 还好github上面有同僚作了framework35版的 2 ILrt中的类目前是不
  • R语言与面向对象的编程(3):R6类

    专注系列化 高质量的R语言教程 本号已支持快捷转载 无需白名单即可转载 本系列将介绍R语言中三个与面向对象的编程 Object Oriented Programming OOP 相关的工具包 proto R6和基础包methods 这是一个