Search code examples
pythondesign-patternsconstructordefault-valuecomposition

Clean pythonic composition with default parameters


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)

Solution

  • 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.