Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most pythonic way to provide defaults for class constructor

Tags:

python

I am trying to stick to Google's styleguide to strive for consistency from the beginning.

I am currently creating a module and within this module I have a class. I want to provide some sensible default values for different standard use cases. However, I want to give the user the flexibility to override any of the defaults. What I am currently doing is I provide a module scoped "constant" dictionary with the default values (for the different use cases) and in my class I give the parameters in the constructor precedence over the defaults.

Finally, I want to make sure that we end with valid values for the parameters.

That's what I have done:

MY_DEFAULTS = {"use_case_1": {"x": 1, "y": 2},
               "use_case_2": {"x": 4, "y": 3}}

class MyClass:
   def __init__(self, use_case = None, x = None, y = None):
      self.x = x
      self.y = y
      if use_case:
         if not self.x:
            self.x = MY_DEFAULTS[use_case]["x"]
         if not self.y:
            self.y = MY_DEFAULTS[use_case]["y"]
      assert self.x, "no valid values for 'x' provided"
      assert self.y, "no valid values for 'y' provided"
   def __str__(self):
      return "(%s, %s)" % (self.x, self.y)  

print(MyClass()) # AssertionError: no valid values for 'x' provided
print(MyClass("use_case_1")) # (1, 2)
print(MyClass("use_case_2", y = 10) # (4, 10)

Questions

  • While technically working, I was wondering whether this is the most pythonic way of doing it?
  • With more and more default values for my class the code becomes very repetitive, what could I do to simplify that?
  • assert seems also for me not the best option at it is rather a debugging statement than a validation check. I was toying with the @property decorator, where I would raise an Exception in case there are invalid parameters, but with the current pattern I want to allow x and y for a short moment to be not truthy to implement the precedence properly (that is I only want to check the truthiness at the end of the constructor. Any hints on that?
like image 552
thothal Avatar asked Mar 18 '20 13:03

thothal


1 Answers

In general if there is more than one way to reasonably construct your object type, you can provide classmethods for alternate construction (dict.fromkeys is an excellent example of this). Note that this approach is more applicable if your use cases are finite and well defined statically.

class MyClass:
   def __init__(self, x, y):
      self.x = x
      self.y = y
   @classmethod
   def make_use_case1(cls, x=1, y=2):
       return cls(x,y)
   @classmethod
   def make_use_case2(cls, x=4, y=3):
       return cls(x,y)

   def __str__(self):
      return "(%s, %s)" % (self.x, self.y)  

If the only variation in the use cases is default arguments then re-writing the list of positional arguments each time is a lot of overhead. Instead we can write one classmethod to take the use case and the optional overrides as keyword only.

class MyClass:
    DEFAULTS_PER_USE_CASE = {
        "use_case_1": {"x": 1, "y": 2},
        "use_case_2": {"x": 4, "y": 3}
    }
    @classmethod
    def make_from_use_case(cls, usecase, **overrides):
        args = {**cls.DEFAULTS_PER_USE_CASE[usecase], **overrides}
        return cls(**args)

    def __init__(self, x,y):
        self.x = x
        self.y = y

    def __str__(self):
        return "(%s, %s)" % (self.x, self.y)

x = MyClass.make_from_use_case("use_case_1", x=5)
print(x)

If you wanted the arguments to be passed positionally that would be more difficult but I imagine this would suit your needs.

like image 199
Tadhg McDonald-Jensen Avatar answered Oct 14 '22 11:10

Tadhg McDonald-Jensen