[Программирование, Data Mining, ООП, R, Data Engineering] ООП в языке R (часть 2): R6 классы

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
10-Ноя-2020 15:31

В прошлой публикации мы разобрали S3 классы, которые являются наиболее популярными в языке R.
Теперь разберёмся с R6 классами, которые максимально приближённые к классическому объектно ориентированному программированию.

Содержание
Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.

Введение
В этой статье мы не будем останавливать на определении термина объектно — ориентированное программирование, и на его принципах, т.е. наследовании, инкапсуляции и полиморфизме.
R6 будут наиболее понятны пользователям Python, и тем кто привык к классическому ООП. В отличие от S3 классов, у R6 методы привязаны к самим объектам, в то время как у S3 всё строится на обобщённых (generic) функциях.
Итак, всё-таки небольшой глоссарий по ООП я предоставлю:

  • Класс — это шаблон, по которому мы можем создавать некоторые объекты. Например, классом может быть кот, собака, автомобиль и так далее.
  • Экземпляр класса — если класс это шаблон, в нашем случае пусть это будет кот, то экземпляр класса это конкретный объект созданный по шаблону. Т.е. мы можем создать любое количество котов, по созданному ранее шаблону.
  • Свойства класса — это переменные которые хранят информацию о каждом отдельном экземпляре класса, например кличка и порода кота.
  • Методы класса — это функции которые хранятся внутри класса, ну к примеру кот может есть, играться и так далее, всё это будут его методы.

Для работы с R6 классами вам необходимо изначально установить и подключить одноимённый пакет R6.
install.packages("R6")
library(R6)

Правила именования
Вам необязательно придерживаться данных правил, но они являются общепринятыми:
  • Имена классов задаются в UpperCamelCase.
  • Имена методов объектов, и его свойства задаются в snake_case.

Так же как и в Python, методы класса могут получить доступ к другим его методам и свойствам через конструкцию self$method().
Создаём собственный класс
Из пакета R6 вы будете использовать всего одну функцию R6Class(). Основные её аргументы:
  • classname — имя класса, должно соответствовать переменной, в которую вы записываете класс;
  • public — принимает список (list()) с публичными свойствами и методами класса.

library(R6)
# создаём класс Cat
Cat <- R6Class(classname = "Cat",
               public = list(
                 name  = "Tom",
                 breed = "Persian",
                 age   = 3,
                 rename = function(name = NULL) {
                   self$name <- name
                   invisible(self)
                 },
                 add_year = function(ages = 1) {
                   self$age <- age + ages
                   invisible(self)
                 }
               )
             )

Мы создали класс Cat, с тремя свойствами name, breed и age, и двумя методами $rename() и $add_year(). Метод $rename() меняет свойство name, как я писал ранее к свойству метод может обращаться через self$name, а метод $add_year() увеличивает возраст кота на заданное количество лет.
Для создания экземпляра класса необходимо использовать встроенный метод $new():
# инициализируем объект класса Cat
tom <- Cat$new()
# смотрим результат
tom

<Cat>
  Public:
    add_year: function (ages = 1)
    age: 3
    breed: Persian
    clone: function (deep = FALSE)
    name: Tom
    rename: function (name = NULL)

Используем метод $rename():
# используем метод rename
tom$rename('Tommy')
# смотрим результат
tom

<Cat>
  Public:
    add_year: function (ages = 1)
    age: 3
    breed: Persian
    clone: function (deep = FALSE)
    name: Tommy
    rename: function (name = NULL)

Как видите объекты созданные с помощью R6 классов меняют свои компоненты на лету, т.е. нет необходимости создавать копии этих объектов.
Т.к. мы создали класс с публичными методами и свойствами то мы имеем к ним доступ вне класса, и соответственно можем их изменять.
# меняем свойство
tom$name <- 'Tom'
# смотрим результат
tom

<Cat>
  Public:
    add_year: function (ages = 1)
    age: 3
    breed: Persian
    clone: function (deep = FALSE)
    name: Tom
    rename: function (name = NULL)

Цепочка методов
Если вы обратили внимание, то при создании методов мы всегда скрываем объект self с помощью оператора invisible(self). Это делается для того, что бы мы могли использовать цепочку методов:
# используем цепочку методов
tom$add_year(1)$add_year(3)
# смотрим результат
tom

<Cat>
  Public:
    add_year: function (ages = 1)
    age: 7
    breed: Persian
    clone: function (deep = FALSE)
    name: Tom
    rename: function (name = NULL)

Опять же такой подход хорошо знаком пользователям Python и JavaScript.
Методы $initialize() и $print()
Важные методы, которые вы наверняка будете использовать при создании R6 классов это методы $initialize() и $print().
Метод $initialize() переопределяет стандартное поведение метода $new(), т.е. используя его вы можете отдельно прокидывать в создаваемый экземпляр класса данные, например имя, породу и возраст кота.
$print() переопределяет метод печати объекта в консоли.
# создаём класс Cat
Cat <- R6Class(classname = "Cat",
               public = list(
                 name  = NA,
                 breed = NA,
                 age   = 0,
                 initialize = function(name, breed, age) {
                   self$name  <- name
                   self$breed <- breed
                   self$age   <- age
                 },
                 print = function(...) {
                   cat("<Cat>: \n")
                   cat("  Name: ", self$name, "\n", sep = "")
                   cat("  Age:  ", self$age, "\n", sep = "")
                   cat("  Breed:  ", self$breed, "\n", sep = "")
                   invisible(self)
                 },
                 rename = function(name = NULL) {
                   self$name <- name
                   invisible(self)
                 },
                 add_year = function(ages = 1) {
                   self$age <- self$age + ages
                   invisible(self)
                 }
               )
)
# создаём экземпляр класса
tom <- Cat$new(name = 'Tom',
               breed = 'Scottish fold',
               age = 1)
# смотрим результат
tom

<Cat>:
  Name: Tom
  Age:  1
  Breed:  Scottish fold

Добавление новых свойств и методов в класс после его определение, метод $set()
Даже после определения класса вы в любой момент можете добавить в него свойства или методы, используя метод $set() и указав уровень приватности.
Ниже приведён пример кода, в котором мы сначала создаём класс с основными методами, после чего через метод $set() добавляем методы $rename() и $add_year().
# создаём класс Cat
Cat <- R6Class(classname = "Cat",
               public = list(
                 name  = NA,
                 breed = NA,
                 age   = 0,
                 initialize = function(name, breed, age) {
                   self$name  <- name
                   self$breed <- breed
                   self$age   <- age
                 },
                 print = function(...) {
                   cat("<Cat>: \n")
                   cat("  Name: ", self$name, "\n", sep = "")
                   cat("  Age:  ", self$age, "\n", sep = "")
                   cat("  Breed:  ", self$breed, "\n", sep = "")
                   invisible(self)
                 }
               )
)
# добавляем метод rename
Cat$set( 'public',
         'rename',
         function(name = NULL)
           {
            self$name <- name
            invisible(self)
})
# добавляем метод add_year
Cat$set( 'public',
         'add_year',
         function(ages = 1) {
           self$age <- self$age + ages
           invisible(self)
         })

При этом, добавленные методы никак не изменят созданные до их добавления экземпляры класса, а будут распространяться лишь на те экземпляры класса, которые будут созданы после их добавления в класс.
Так же вы можете запретить переопределение методов класса, используя при его создании аргумент lock_class = TRUE. В дальнейшем это поведение можно изменить, и разблокировать класс методом $unlock(), и опять заблокировать методом $lock().
# Создаём класс с блокировкой переопределения методов
Simple <- R6Class("Simple",
  public = list(
    x = 1,
    getx = function() self$x
  ),
  lock_class = TRUE
)
# При попытке переопределить метод мы получим ошибку
Simple$set("public", "y", 2)
# Разблокируем класс
Simple$unlock()
# Теперь мы можем переопределять существующие свойства и методы класса
Simple$set("public", "y", 2)
# Повторно блокируем класс
Simple$lock()

Пример кода взят из официальной документации к пакету R6, автор Winston Chang

Наследование
R6 классы поддерживают механизм наследования, т.е. вы можете создавать супер классы и подклассы, которые будут наследовать от супер классов методы и свойства. При необходимости вы можете переопределять методы супер класса.

На изображении класс "Животные" является главным супер классом, его подклассом являются "Домашние животные" и "Дикие животные", т.е. они наследуют все свойства и методы класса (шаблона) животные, но могут их переопределять, а так же могут иметь свои дополнительные методы и свойства.
Далее мы создаём подклассы "Кот" и "Собака", для которых класс "Домашние животные" уже будет родительским, т.е. супер классом. Так же мы создаём классы "Олень" и "Медведь", для которых супер классом будет "Дикие животные".
Эту цепочку можно было продолжить породами котов, собак, оленей и медведей.
Для реализации наследования в R6 классах необходимо использовать аргумент inherit.
library(R6)
# создаём супер класс Cat
Cat <- R6Class(classname = "Cat",
               public = list(
                 name  = NA,
                 breed = NA,
                 age   = 3,
                 initialize = function(name, breed, age) {
                   self$name  <- name
                   self$breed <- breed
                   self$age   <- age
                 },
                 rename = function(name = NULL) {
                   self$name <- name
                   invisible(self)
                 },
                 add_year = function(ages = 1) {
                   self$age <- self$age + ages
                   invisible(self)
                 }
               )
             )
# создаём подкласс ScottishCat
ScottishCat <- R6Class("ScottishCat",
                       inherit = Cat,
                       public = list(
                         breed = "Scottish Fold",
                         add_year = function() {
                              cat("Увеличили возраст ", self$name, " на 1 год", sep = "")
                              super$add_year(ages = 1)
                         },
                         initialize = function(name,  age, breed = "Scottish Fold") {
                              self$name  <- name
                              self$breed <- breed
                              self$age   <- age
                               }
                             )
)
# создаём экземпляр класса
scottish <- ScottishCat$new(name = 'Arnold', age = 1)
# используем метод подкласса
scottish$add_year()

В данном примере мы создали супер класс Cat, и подкласс ScottishCat. В подклассе мы переопредели метод $add_year(), тем не менее, мы можем внутри подкласса использовать унаследованные от супер класса методы обращаясь к ним через super$method().
Приватные методы и свойства
В аргумент public функции R6Class() мы передаём методы и свойства класса с общим доступом, т.е. эти методы и свойства доступны как внутри класса, так и за его пределами.
Так же вы можете создавать приватные свойства и методы, которые будут доступны исключительно внутри класса. Такие свойства и методы необходимо передавать в аргумент private, внутри класса доступ к приватным методам и свойствам осуществляется через private$methode_name.
library(R6)
# создаём класс
User <- R6Class('User',
                public = list(
                  name = NA,
                  initialize = function(name, password, credits = 100) {
                    self$name        <- name
                    private$password <- password
                    private$credits  <- credits
                  },
                  print = function(...) {
                    cat("<User>: ", self$name, sep='')
                  },
                  get_credits = function() {
                    cat(private$credits)
                  }
                ),
                private = list(
                  password = NULL,
                  credits  = NULL
                )
              )
# экземпляр класса
user_1 <- User$new('Alex', 'secretpwd')
# метод который использует приватное свойство
user_1$get_credits()

100

В данном случае мы получили значение приватного свойства credits через специальный метод. Но если мы напрямую попробуем обратиться к данному свойству, то у нас ничего не получится:
# обращение к приватному свойству вне класса
user_1$credits

NULL

Активные методы
Активные методы для пользователя выглядят как свойства, но при обращении к ним они выполняют заданную функцию.
При каждом обращении к активному методу будет выполняться определённая функция, это удобно к примеру для запуска генератора случайных чисел, или выбора случайного значения из вектора.
Давайте вернёмся к нашему коту, и напишем класс, в котором будет свойство dictionary, в котором будет вектор звуков, которые может воспроизводить кот. И активный метод $say(), который случайным образом будет выводить одну из заданных фраз.
library(R6)
# создаём класс Cat
Cat <- R6Class(classname = "Cat",
               public = list(
                 name  = NA,
                 breed = NA,
                 dictionary = NA,
                 initialize = function(name, breed, dictionary) {
                   self$name         <- name
                   self$breed        <- breed
                   self$dictionary   <- dictionary
                 }
               ),
               active = list(
                 say = function(value) {
                   if (missing(value)) {
                      return(paste0(self$name, ' say ', sample(self$dictionary, size = 1)))
                    } else {
                      self$dictionary <- value
                    }
                 }
              )
           )
# создаём экземпляр класса
cat <- Cat$new('Tom',
               'Persian',
               c('meow', 'mrrr', 'frrr'))
# запускаем активный метод
cat$say

[1] "Tom say meow"

Как видите к активным методам мы обращаемся как к свойствам, т.е. без скобок и аргументов, но при этом выполняется функция say.
В функции мы реализовали проверку if (missing(value)), т.е. мы проверяем если идёт обращение к активному методу, то мы просто выводим случайную фразу из self$dictionary. Если в активный метод передать значение, то будет выполняться условие else, в нашем случае переопределение self$dictionary.
# переопределяем свойство
cat$say <- c('grrr', 'waw', 'chfw')
# используем активный метод
cat$say

"Tom say grrr"

Финализатор класса
Вы можете добавить в класс специальный метод $finalize, который будет запускаться после удаления объекта при завершении R сессии.
Это полезно в тех случаях когда ваш класс работает с файлами или базами данных, тогда вы можете написать финализатор для того, что бы быть уверенным, что соединение с файлом или базой будет разорвано.
TemporaryFile <- R6Class("TemporaryFile", list(
  path = NULL,
  initialize = function() {
    self$path <- tempfile()
  },
  finalize = function() {
    message("Cleaning up ", self$path)
    unlink(self$path)
  }
))

Пример кода взят из книги Advanced R, автор Hadley Wickham

Добавление R6 классов в пакет
Ещё одно отличие R6 классов от S3 заключается в том, что вам не надо прописывать ваши R6 классы в фале NAMESPACE. Достаточно просто включить пакет R6 в поле Imports файла DESCRIPTION.
Полезные ссылки
Данная статья не является свободным переводом какой-либо англоязычной публикации, но при её написании я пользовался следующими источниками:

Заключение
Как вы убедились R6 классы это реализация классического объектно ориентированного программирования в языке R.
Тем не менее данные класс используется в R достаточно редко, т.к. родной, и общепринятой реализацией ООП в R по-прежнему считаются S3 классы.
Подписывайтесь на мой канал R4marketing в Telegram и YouTube.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_data_mining, #_oop (ООП), #_r, #_data_engineering, #_r, #_oop (ООП), #_jazyk_r (язык R), #_klassy (классы), #_r6_klassy (R6 классы), #_programmirovanie (
Программирование
)
, #_data_mining, #_oop (
ООП
)
, #_r, #_data_engineering
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 18:17
Часовой пояс: UTC + 5