How to properly set Python class attributes from init arguments
Asked Answered
N

1

4

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.

Neoplasm answered 19/2, 2022 at 21:29 Comment(2)
why do you need a decorator for setting instance variables? maybe usedataclass or namedtupleMalindamalinde
C++ isn't really any more "efficient"; the typing is just spread out across more syntactic constructs. I would be more concerned about the fact that you have so many separate arguments to your class.Jacobson
L
7

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)
Legato answered 19/2, 2022 at 21:58 Comment(1)
Dataclasses are the best. I think it's also worth mentioning that while they give you all those nice behaviors by default, they're all customizable as well. Loads of options for when the stock setup isn't quite what you need. Particularly useful is __post_init__, for when you want the convenience of the supplies initializer but also want to do further setup afterward.Twowheeler

© 2022 - 2024 — McMap. All rights reserved.