r-lib / R6

Encapsulated object-oriented programming for R
https://R6.r-lib.org
Other
403 stars 56 forks source link

What is the best way to mimic Python Descriptor Class using R6? #207

Closed DyfanJones closed 3 years ago

DyfanJones commented 3 years ago

Hi All,

This is a question regarding how to mimic python descriptor classes using R6. Currently I have only managed to do this through the use of active bindings and I was wondering is there a simpler way?

Example of a basic python descriptor class from https://www.datacamp.com/community/tutorials/python-descriptors

# python example
class Descriptor:
    def __init__(self):
        self.__fuel_cap = 0
    def __get__(self, instance, owner):    
        return self.__fuel_cap
    def __set__(self, instance, value):
        if isinstance(value, int):
            print(value)
        else:
            raise TypeError("Fuel Capacity can only be an integer")

        if value < 0:
            raise ValueError("Fuel Capacity can never be less than zero")

        self.__fuel_cap = value

    def __delete__(self, instance):
        del self.__fuel_cap

class Car:
    fuel_cap = Descriptor()
    def __init__(self,make,model,fuel_cap):
        self.make = make
        self.model = model
        self.fuel_cap = fuel_cap

    def __str__(self):
        return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make,self.model,self.fuel_cap)

car2 = Car("BMW","X7",40)
print(car2)

# 40
# BMW model X7 with a fuel capacity of 40 ltr.

Mimicking this behaviour through the use of active bindings:

# R6 example
Descriptor = R6Class("Descriptor",
  public = list(
   ..fuel_cap = 0,
   initialize = function() {
     self$..fuel_cap = 0
   }
  ),
  active=list(
   descriptor = function(value){
     # get method
     if(missing(value))
       return(private$.get())

     # delete method
     if(is.null(value))
       return(private$.delete(value))

     # set method
     return(private$.set(value))
   }
  ),

  private = list(
   .get = function(){
     self$..fuel_cap
   },

   .set = function(value){
     if(inherits(value, "integer"))
       print(value)
     else
       stop("Fuel Capacity can only be an integer")

     if (value < 0)
       stop("Fuel Capacity can never be less than zero")

     self$..fuel_cap = value
   },

   .delete = function(value){
     self$..fuel_cap = NULL
   }
  )
)

Car = R6Class("Car",
  public = list(
    make = NULL,
    model = NULL,
    initialize = function(make, model, fuel_cap){
      self$make = make
      self$model = model
      self$fuel_cap = fuel_cap
    },

    print = function(...){
      return(cat(sprintf("%s model %s with a fuel capacity of %i ltr.", self$make,self$model, self$fuel_cap)))
    }
  ),

  private = list(
    .fuel_cap = Descriptor$new()
  ),

  active = list(
    fuel_cap = function(value){
      if(missing(value))
        return(private$.fuel_cap$descriptor)
      private$.fuel_cap$descriptor = value
    }
  )
)

car2 = Car$new("BMW","X7", 40L)
car2

# 40
# BMW model X7 with a fuel capacity of 40 ltr.

Is there a simpler method?

wch commented 3 years ago

Can you provide a short description of what behavior you're trying to achieve?

DyfanJones commented 3 years ago

Sorry for the super late reply. I will close this ticket as I believe I have a possible solution for my initial problem (https://github.com/DyfanJones/sagemaker-r-sdk/blob/master/R/amazon_hyperparameter.R#L5-L123)