Reducing syntactic cruft when a method accesses instance variables?


The following code is a simplified example of a task I'm working on, using Python, that seems to be a natural fit for an OOP style:

class Foo:
  def __init__(self):
    self.x = 1
    self.y = 1
    self.z = 1
  def method(self):
    return bar(self.x,self.y,self.z)

def bar(x,y,z):
  return x+y+z

f = Foo()
print(f.method())

In the example code above, I have three instance variables in my object, but in my actual application it would be more like 10 or 15 variables, and if I implement what I have in mind in this style, then I'm going to end up with a lot of code that looks like this:

return bar(self.a.self.b,self.c,self.d,self.e,self.f,self.g,self.h,self.i)

Wow, it sure would be nice to be able to write this in a style more like this:

return bar(a,b,c,d,e,f,g,h,i)

That would be a lot more concise and readable. One way to do this might be to rewrite bar so that it takes a Foo object as an input rather than a bunch of scalar variables, but I would prefer not to do that. Actually, that would just push the syntactic cruft down into the bar function, where I guess I would have code that looked like this:

  def bar(f):
    return f.a+f.b+f.c

Is there a nicer way to handle this? My understanding is that without the "self.", I would be referencing class variables rather than instance variables. I thought about using a dictionary, but that seems even cruftier, with all the ["a"] stuff. Might there be some automated way to take a dictionary with keys like "a","b","c",... and kind of unload the values into local variables named a, b, c, and so on?


Answer

You can use __dict__ to create attributes from data of varying length, and then use classmethod to sum the attributes passed:

import string
class Foo:
  def __init__(self, data):
    self.__dict__ = dict(zip(string.ascii_lowercase, data))
  @classmethod
  def bar(cls, instance, vals = []):
    return sum(instance.__dict__.values()) if not vals else sum(getattr(instance, i) for i in vals)

f = Foo(range(20))
print(Foo.bar(f))
print(Foo.bar(f, ['a', 'c', 'e', 'k', 'm']))

Output:

190
28