Search code examples
pythonpython-dataclassesnamedtuplepython-attrs

How do I avoid the "self.x = x; self.y = y; self.z = z" pattern in __init__?


I see patterns like

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

quite frequently, often with a lot more parameters. Is there a good way to avoid this type of tedious repetitiveness? Should the class inherit from namedtuple instead?


Solution

  • Edit: If you have python 3.7+ just use dataclasses

    A decorator solution that keeps the signature:

    import decorator
    import inspect
    import sys
    
    
    @decorator.decorator
    def simple_init(func, self, *args, **kws):
        """
        @simple_init
        def __init__(self,a,b,...,z)
            dosomething()
    
        behaves like
    
        def __init__(self,a,b,...,z)
            self.a = a
            self.b = b
            ...
            self.z = z
            dosomething()
        """
    
        #init_argumentnames_without_self = ['a','b',...,'z']
        if sys.version_info.major == 2:
            init_argumentnames_without_self = inspect.getargspec(func).args[1:]
        else:
            init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]
    
        positional_values = args
        keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
        attribute_values = positional_values + keyword_values_in_correct_order
    
        for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
            setattr(self,attribute_name,attribute_value)
    
        # call the original __init__
        func(self, *args, **kws)
    
    
    class Test():
        @simple_init
        def __init__(self,a,b,c,d=4):
            print(self.a,self.b,self.c,self.d)
    
    #prints 1 3 2 4
    t = Test(1,c=2,b=3)
    #keeps signature
    #prints ['self', 'a', 'b', 'c', 'd']
    if sys.version_info.major == 2:
        print(inspect.getargspec(Test.__init__).args)
    else:
        print(inspect.signature(Test.__init__))