3

I have a Python class MyObject (a subclass of tuple) and another class for a set of these objects, MyObjectSet (a subclass of set). I’d like that, for any non-builtin method that I define for MyObject, a method of the same name be defined for MyObjectSet with value equal to the sum of the method over the contents of the MyObjectSet.

I had thought that something like the code below would work, but the result doesn’t match my intended outcome. In practice MyObject and MyObjectSet have a lot more to them and are justified.

class MyObject(tuple):
    def stat_1(self):
        return len(self)
    
    def stat_2(self):
        return sum(self)

class MyObjectSet(set):
    pass

for stat_name in dir(MyObject):
    if not stat_name.startswith("__"):
        stat_func = getattr(MyObject, stat_name)
        if callable(stat_func):
            setattr(MyObjectSet, stat_name, lambda S: sum(stat_func(p) for p in S))

if __name__ == "__main__":
    S = MyObjectSet(MyObject(t) for t in [(1,2), (3,4)])
    
    result, expected = S.stat_1(), sum(p.stat_1() for p in S)
    print(f"S.size() = {result}, expected {expected}")
    
    result, expected = S.stat_2(), sum(p.stat_2() for p in S)
    print(f"S.sum() = {result}, expected {expected}")

Is there any way to achieve this functionality?

martineau
  • 119,623
  • 25
  • 170
  • 301

2 Answers2

1

replace your lambda with this:

lambda S, f=stat_func: sum(f(p) for p in S)

It copies the stat_func into f, instead of capturing a reference to it, which was what happened in your original code (so all stat_funcs inside your different lambdas ended up being the last value assigned to the stat_func in the for loop.

Adam.Er8
  • 12,675
  • 3
  • 26
  • 38
0

You can simply override __getattr__ to treat any possible method call as a summing wrapper around the object's method of the same name. This simple example will just raise an AttributeError if the underlying method doesn't exist; you may want to catch the exception and raise another error of your own.

class MyObjectSet(set):
    def __getattr__(self, mn):
        return lambda: sum(methodcaller(mn)(x) for x in self)
                    
chepner
  • 497,756
  • 71
  • 530
  • 681