1

Suppose I want to compose two objects and I'd like to be able to be able to define multiple constructors that respect the default arguments.

class A:
   def __init__(x,y=0):
       self.x = x
       self.y = y

class B:
    def __init__(w,objA, z=1):
         self.w = w
         self.objA = objA
         self.z = z

I'd like to have multiple constructors for B which allow me to either pass an object A or pass parameters and then construct A. Looking at What is a clean, pythonic way to have multiple constructors in Python? I can do:

class B:
    def __init__(w, objA, z=1):
         self.w = w
         self.objA = objA
         self.z=z

    @classmethod
    def from_values(cls,w,z,x,y):
        objA = A(x,y)
        return cls(w,objA,z)

The problem is that the default values are being overwritten and I'd like to keep them. The following of course does not work.

@classmethod
def from_values(cls,w,x,**kwargs):
    objA = A(x,**kwargs)
    return cls(w,objA,**kwargs)

So it seems I'm stuck with remembering the default values and calling them like this

@classmethod
def from_values(cls,w,x,z=1,y=0):
    objA = A(x,y)
    return cls(w,objA,z)

This is not what I want since I'd rather have the objects themselves handle the default values and not be forced remember the default values. I could do one better than the above and use:

 @classmethod
 def from_values(cls,w,x,z=1,**kwargs):
    objA = A(x,**kwargs)
    return cls(w,objA,z)

But in this case I still need to "remember" the default value for z. Is there a Pythonic solution to this? Is this a design problem? Can someone point me to a good design pattern or best practices? This problem compounds when composing with several objects...

class L:
    def __init__(objM,objN):
        self.objM = objM
        self.objN = objN

    @classmethod
    def from_values(cls, m1,m2,m3,n1,n2):
        objM = M(m1,m2,m3)
        objN = N(n1,n2)
        return cls(objM, objN)
moquant
  • 15
  • 3

1 Answers1

0

First I would argue that this isn't what you want to do. As this scales up, trying to call your constructor will be tedious and error-prone.

You can solve both of our problems with an explicit dictionary.

class A:
    def __init__(self, config):
        self.x = config.get('x')
        assert self.x  # if you need it
        self.y = config.get('y', 0)

class B:
    def __init__(self, b_config, objA):
        self.w = b_config.get('w')
        self.objA = objA
        self.z = b_config.get('z', 1)

    @classmethod
    def from_values(cls,b_config,a_config):
        return cls(b_config, A(a_config))

B.from_values({'w':1, 'z':2}, {'x': 3, 'y': 4})

It's probably not as clever or neat as what you're looking for, but it does let you construct from an A if you already have it, or to pass in a configurable set of parameters in a more structured way.

  • Yes, I wanted to avoid using explicit dictionaries in the constructor. I agree that my current paradigm will become very messy but I was hoping I could design my way out of it, potentially by completely reworking the classes/composition if necessary. Maybe I'm using composition in the wrong place since this problem seems to happen to me often. – moquant Oct 02 '18 at 19:36
  • Or maybe a better design is to hold off on default parameters and let another object wrap both A and B providing defaults when needed? – moquant Oct 02 '18 at 19:43
  • @moquant I'm not too comfortable commenting on something this abstract, but it certainly simplifies some issues. –  Oct 02 '18 at 19:50
  • It just seems rather a lot of hassle just to let the caller avoid having to construct their own `A`. –  Oct 02 '18 at 19:50
  • 1
    yeah, I feel like I'm jumping through hoops. Maybe the best plan is just not to let B have an option to construct A. I am anticipating reading in data in a specific form. Maybe I will leave it to the data reader to construct `A` out of the data to pass to `B` somewhere else in the pipeline instead of making a mess of things just so I can call `B(data)` directly. – moquant Oct 02 '18 at 19:59
  • I'll accept now since your answer certainly answers accomplishes the goal of the question, but chatting back and forth has helped me more so as I think ultimately I just don't want to do what I claimed I wanted in my question (which is also in your answer). – moquant Oct 02 '18 at 20:00