pythonargument-unpacking

How to pass unpacking as an input parameter to initialize a member class object in Python?


For example, in the following code, I want to pass 6 parameters by unpacking *num to initialize a1, a2, a3 in class A and b1, b2, b3 in class B. However, there will be an error because A only has 4 members. How do I pass 6 parameters to initialize A, automatically initialize the first 3 parameters a1, a2, a3, and use the last 3 parameters to initialize b1, b2, b3 in member object b?

The source of the question is because I want to use vars(self) to extract all member data of the subclass objects of the Base class, just like the get_data method, so that there is no need to display the member names every time, otherwise there will be too much repetitive code, such as return [self.b1, self.b2, self.b3], because writing each subclass in this way requires handwritten variable names.

The example code is as follows. When executing A(*num), an error message will appear indicating that there are too many parameters

@dataclass
class Base():
    def __str__(self):
        data = ','.join([str(x) for x in vars(self).values()])
        return data

    def get_data(self):
        return ','.join([str(x) for x in vars(self).values()])

@dataclass
class B(Base):
    b1: int = 0
    b2: int = 0
    b3: int = 0

@dataclass
class A(Base):
    a1: int = 0
    a2: int = 0
    a3: int = 0
    b: B = None

num = [1,2,3,4,5,6]
my_data = A(*num)

Solution

  • Initialisation of the member attributes of both A and B can be achieved by having A inherit from B:

    from dataclasses import dataclass
    
    @dataclass
    class Base:
        def __str__(self):
            return ",".join(map(str, vars(self).values()))
        def __repr__(self):
            return ",".join(map(str, vars(self).items()))
    
    @dataclass
    class B(Base):
        b1: int
        b2: int
        b3: int
    
    @dataclass
    class A(B):
        a1: int
        a2: int
        a3: int
    
    num = [1, 2, 3, 4, 5, 6]
    my_data = A(*num)
    print(my_data)
    print(repr(my_data))
    

    Note that B will be initialised before A.

    Output:

    1,2,3,4,5,6
    A(b1=1, b2=2, b3=3, a1=4, a2=5, a3=6)
    

    Observation:

    Do you actually need a dataclass? Using a more "traditional" approach makes your requirement much easier to implement.

    In accordance with the requirement to unpack the list when constructing the class, you could do this:

    class Base:
        def __str__(self):
            return ",".join(map(str, vars(self).values()))
        def __repr__(self):
            return ",".join(map(str, vars(self).items()))
    
    class B(Base):
        def __init__(self, *args):
            self.b1, self.b2, self.b3 = args
    
    class A(B):
        def __init__(self, *args):
            self.a1, self.a2, self.a3 = args[:3]
            super().__init__(*args[3:])
    
    data = [1,2,3,4,5,6]
    print(A(*data))
    print(repr(A(*data)))
    

    Output:

    1,2,3,4,5,6
    ('a1', 1),('a2', 2),('a3', 3),('b1', 4),('b2', 5),('b3', 6)
    

    In this case A is initialised before B.

    Taking matters a step further where there's a more complex subclassing pattern then:

    class Base:
        def __str__(self):
            return ",".join(map(str, vars(self).values()))
    
        def __repr__(self):
            return ",".join(map(str, vars(self).items()))
    
    
    class C(Base):
        def __init__(self, *args):
            self.c1, self.c2, self.c3 = args[:3]
    
    
    class B(C):
        def __init__(self, *args):
            self.b1, self.b2, self.b3 = args[:3]
            super().__init__(*args[3:])
    
    
    class A(B):
        def __init__(self, *args):
            self.a1, self.a2, self.a3 = args[:3]
            super().__init__(*args[3:])
    
    
    data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(A(*data))
    print(repr(A(*data)))
    

    Output:

    1,2,3,4,5,6,7,8,9
    ('a1', 1),('a2', 2),('a3', 3),('b1', 4),('b2', 5),('b3', 6),('c1', 7),('c2', 8),('c3', 9)