oldselflearner1959
oldselflearner1959

Reputation: 653

Dealing with many parameters/class attributes in Python

I'm creating a Python class that will use its parameters to define two other classes within this class. For example, ClassA contains ClassB and ClassC. Because of this, ClassA has a lot of parameters.

I'm currently solving this using **kwargs for classB and classC arguments, and saving only the input parameters for classA parameters. For example:

class A:
    def __init__(self, a1, a2, a3, **kwargs):
        self.a1 = a1
        self.a2 = a2
        self.a3 = a3

        # Get B and C kwargs
        default_params = {
            'b1': kwargs.get('b1', 1),
            'b2': kwargs.get('b1', 2),
            'c1': kwargs.get('c1', 1),
            'c2': kwargs.get('c1', 2),          
        }
        for param in default_params:
            self.__dict__[param] = default_params[param]

        B = classB(a1=self.a1, b1=self.b1, b2=self.b2)
        C = classC(a2=self.a2, c1=self.c1, c2=self.c2)

Is this a worse solution compared to the below?

class A:
    def __init__(
        self,
        a1, a2, a3,
        b1, b2, c1, c2):
        self.a1 = a1
        self.a2 = a2
        self.a3 = a3
        self.b1 = b1
        self.b2 = b2
        self.c1 = c1
        self.c2 = c2

My concern is if arguments for class B and C are super numerous, then class A's attributes seems too many. On the other hand, the first approach seems to take up more lines anyway.

Another way is if I pass only kwargs to classB and classC, but this will have an error when there are some parameters not present in one of the classes.

Is there a pythonic way I'm failing to see here for classes with many attributes and utilising this kind of design pattern?

Upvotes: 1

Views: 281

Answers (1)

MisterMiyagi
MisterMiyagi

Reputation: 50086

Your primary criteria should be to avoid emulating what Python provides natively – namely parameters and defaults, as well as instance attributes.

Since __init__ already knows all the attributes and defaults it expects (via default_params), add them to the signature directly. Avoid storing arguments for classB and classC on A – if needed, fetch them from the instances.

class A:
    # handle parameters/defaults natively
    def __init__(self, a1, a2, a3, b1=1, b2=2, c1=1, c2=2):
        self.a1 = a1
        self.a2 = a2
        self.a3 = a3
        # do not store B/C attributes, store B/C directly
        self.b = classB(a1=self.a1, b1=b1, b2=b2)
        self.c = classC(a2=self.a2, c1=c1, c2=c2)

This is functionally equivalent to the first case (minus exposing all arguments) but uses native Python functionality.


If A has to provide additional functionality, add them explicitly:

  • If A should take (and ignore) additional parameters, have an additional catch-all **kwargs:

    class A:
        # discard additional parameters/defaults explicitly
        def __init__(self, a1, a2, a3, b1=1, b2=2, c1=1, c2=2, **_):
            ...
    
  • If A should expose classB and/or classC fields, provide them from the instances.

    class A:
        ...
    
        @property
        def b1(self):
            return self.b.b1
    

Upvotes: 1

Related Questions