Python decorator to automatically define __init__ variables

I disagree that this is useful. I find that forcing developers (including myself) to type out the painful boilerplate of member variable initiation is a good way to deter people from having __init__ methods that accept a ridiculous number of arguments, which are turned into a ridiculous number of member variables.

This happens a lot when someone wants to extend the features available in a class through the use of extra arguments, feature flags, and boolean switch variables that control customized instantiation. I consider all of those to be deficient ways to handle the need for accommodating new or optional extended complexity.

Being required to type out this particular kind of boilerplate is like a tax on class bloatedness. If you find yourself accepting so many args in __init__ that you need this feature, it's usually a good indicator that you should refactor your design with smaller, compartmentalized classes, maybe a MixIn design.

Nonetheless, here is one way to do it without the misdirection of the decorator. I didn't make an attempt to handle *args but then again in this particular case you would have to define special logic for what unnamed positional arguments meant anyway.

def init_from_map(obj, map):
    for k,v in map.iteritems():
        if k not in ["self", "kwargs"]:
            setattr(obj, k, v)
        elif k == "kwargs":
            for kk, vv in v.iteritems():
                setattr(obj, kk, vv)

class Foo(object):
    def __init__(self, x, y, **kwargs):
        init_from_map(self, locals())

f = Foo(1, 2, z=3)
print f.x, f.y, f.z
print f.__dict__

Prints:

1 2 3
{'x': 1, 'y': 2, 'z': 3}

You could do this:

def __init__(self, x, y):
    self.__dict__.update(locals())
    del self.self   # redundant (and a circular reference)

But this is probably not a real improvement, readability-wise.


Here is my first try at the decorator:

[EDIT second try: I added handling defaults for variables and checking for valid keywords. Thanks ivan_pozdeev ]

[EDIT 3: Added check for defaults is not None]

def instanceVariables(func):
    def returnFunc(*args, **kwargs):
        selfVar = args[0]

        argSpec = inspect.getargspec(func)
        argumentNames = argSpec[0][1:]
        defaults = argSpec[3]
        if defaults is not None:
            defaultArgDict = dict(zip(reversed(argumentNames), reversed(defaults)))
            selfVar.__dict__.update(defaultArgDict)

        argDict = dict(zip(argumentNames, args[1:]))
        selfVar.__dict__.update(argDict)


        validKeywords = set(kwargs) & set(argumentNames)
        kwargDict = {k: kwargs[k] for k in validKeywords}
        selfVar.__dict__.update(kwargDict)

        func(*args, **kwargs)

    return returnFunc

Here is a example:

class Test():

    @instanceVariables
    def __init__(self, x, y=100, z=200):
        pass

    def printStr(self):
        print(self.x, self.y, self.z)

a = Test(1, z=2)

a.printStr()

>>> 1 100 2

For those who might find this post, but are interested in a Python 3.7+ solution (and as Python 2 End Of Life is the Jan 1, 2020 ;-), you can use the Python standard lib dataclasses.

from dataclasses import dataclass

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Will add, among other things, a __init__() that looks like:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand