title: Avoid `self.` and variable boilerplate in classes
body: <p><strong>Question</strong> What is the proper way to wrap data in Python? Is the <code>__init__</code> and <code>self.</code> boilerplate needed?</p>
  
  <p><strong>As a class</strong> Sometimes I wish to create classes that represent Data, but don't have complex computational mechanics inside. For example:</p>
  
  
  
  <pre class="lang-py prettyprint-override"><code>class DataClass:
    def __init__(self, a, b, c):
      self.a = b
      self.b = b
      self.c = c
  
    def sum(self):
      return self.a + self.b + self.c
  
  
  dc = DataClass(1,2,3)
  
  print(dc.sum())
  </code></pre>
  
  <p>I dislike this code, the mathematics of <code>a + b + c</code> is hard to read due to the <code>self.</code> and the constructor is boilerplate. Moreover, I would create this class whenever I noticed that a tuple or dict around a, b and c is getting messy. In particular, tuples are ugly when the returned values change in amount or order, as happend with <code>cv2.findContours</code> between opencv versions 3 and 4 (returning 3, respectively 2 values). Lastly, I often copy-paste boilerplate stuff, easily allowing for painful mistakes, as is the case here with <code>self.a = b</code>.</p>
  
  <p><strong>As a function</strong> Therefore I often do something like this:</p>
  
  <pre class="lang-py prettyprint-override"><code>def DataFunc(a, b, c):
    class Result:
      def sum(self):
        return a + b + c
    return Result()
  
  
  df = DataFunc(1,2,3)
  
  print(df.sum())
  </code></pre>
  
  <p>It works and it is significantly more transparent (imho), but also a bit weird w.r.t. types, i.e.</p>
  
  <pre class="lang-py prettyprint-override"><code>rf2 = ResultFunc(1,2,3)
  print(type(rf) == type(rf2)) # -&gt; False
  </code></pre>
  
  <p>Moreover, the data members are not accessible (<code>rf.a</code>, <code>rf.b</code>, etc). </p>
  
  <p><strong>As a class with an annotator</strong> Finally, one can create an annotator to add the constructor:</p>
  
  <pre class="lang-py prettyprint-override"><code>def add_constructor(*vars):
      def f(class_):
          def init(self, *args, **kwargs):
              unasigned = set(vars[len(args):]) - set(kwargs.keys())
              if len(unasigned) &gt; 0:
                  raise ValueError(f"Missing argument(s): {' '.join(unasigned)}")
              for name, value in zip(vars, args):
                  setattr(self, name, value)
              for name, value in kwargs.items():
                  setattr(self, name, value)
  
          setattr(class_, '__init__', init)
          return class_
      return f
  
  
  @add_constructor('a', 'b', 'c')
  class DataAnnot:
      def sum(self):
          return self.a + self.b + self.c
  
  da = DataAnnot(1,2,c=3)
  print(da.sum())
  </code></pre>