How to properly set Python class attributes from init arguments

Viewed 634

As a Python programmer, I frequently declare classes similar to

class Foo:
  def __init__(self, attr1, attr2, attr3, attr4, attr5, attr6, attr7, attr8, attr9):
    self.attr1 = attr1
    self.attr2 = attr2
    ...
    self.attr9 = attr9

    # init your class

The problem is that I do that over and over, and it feels very inefficient. In C++ the way to do this would be

class Foo {
  public:
    Foo(int, int, int, int, int);
    
  private:
    int attr1, attr2, attr3, attr4, attr5;
    
};

Foo::Foo(int attr1, int attr2, int attr3, int attr4, int attr5) : 
    attr1(attr1), attr2(attr2), attr3(attr3), attr4(attr4), attr5(attr5) {
    // init your class
}

Which I feel like it's slightly more efficient. My question is: is there a standard way to avoid having to attribute the arguments to the class for every argument? I can think of ways to program it, for example

def attribute_setter(*arg_names):
  def wrapper(func):
    def setter_init(self, *args, **kwargs):
      for i, name in enumerate(arg_names):
        if i < len(args):
          setattr(self, name, args[i])
        elif name in kwargs:
          setattr(self, name, kwargs[name])
      func(self, *args, **kwargs)
    return setter_init
  return wrapper

class Foo:
  @attribute_setter('attr1', 'attr2', 'attr3', 'attr4', 'attr5', 'attr6', 'attr7', 'attr8', 'attr9')
  def __init__(self, *args, **kwargs):
    # init your class
    pass

Which yields

>>> foo = Foo(1, 2, 3, 4, 5, attr6=6, attr7=7, attr8=8, attr9=9)
>>> foo.attr1
1
>>> foo.attr9
9

With a lot more error handling, this decorator might become safe to use, but my concern is the readability of the code and making sure I'm not violating any good practice principle.

1 Answers

This is exactly what the dataclasses module is for. You use it like this:

from dataclasses import dataclass


@dataclass
class Foo:
  attr1: str
  attr2: int
  attr3: bool
  # ...etc...

That gives you a class with an implicit __init__ method that takes care of all the parameter setting for you. You also get things like a useful __repr__ and __str__ predefined:

>>> myvar = Foo(attr1="somevalue", attr2=42, attr3=False)
>>> myvar
Foo(attr1='somevalue', attr2=42, attr3=False)
>>> print(myvar)
Foo(attr1='somevalue', attr2=42, attr3=False)
Related