Using a decorator to apply an inherited method to all child objects of the inherited type I wanted to share this and get some feedback.  My goal was to be able to have an object with methods:


class Thing(object):
    def __init__(self, data=123):
        self.data = data

    def add_one(self):
        self.data += 1

    def add_number(self, number):
        self.data += number



And then a collection object, MetaThing that can hold several Thing objects, which inherits from Thing like this:


class MetaThing(Thing):

    def __init__(self):
        super(MetaThing, self).__init__()

        self.list = [1, 2, 3, 4, 5]
        self.things = []
        self.setupThings()

    def setupThings(self):

        for i in self.list:
            thing = Thing(data=i)
            self.things.append(thing)



Finally, I want to be able to apply the methods in Thing to all the Thing instances stored in MetaThing.things when those same methods are called on MetaThing.

Doing this explicitly creates methods like this:


    def add_one(self):
        for t in self.things:
            t.add_one()

    def add_number(self, number):
        for t in self.things:
            t.add_number(number)



So I started wondering if this would be a good situation for a decorator, so I wrote one that I think does what I want:

def allthings(func):
    def new_func(*args, **kwargs):
        self = args[0]
        for thing in self.things:
            arglist = list(args)
            arglist[0] = thing
            func(*arglist, **kwargs)

    return new_func


So now, the MetaThing method can use the @allthings decorator to call an inherited method on all child objects without rewriting the for loop every time:


class MetaThing(Thing):

    def __init__(self):
        super(MetaThing, self).__init__()

        self.list = [1, 2, 3, 4, 5]
        self.things = []
        self.setupThings()

    def setupThings(self):

        for i in self.list:
            thing = Thing(data=i)
            self.things.append(thing)

    @allthings
    def add_one(self):
        self.add_one()

    @allthings
    def add_number(self, number):
        self.add_number(number)



Finally this block verifies that it's working:


if __name__ == '__main__':

    meta = MetaThing()

    meta.add_one()

    print [t.data for t in meta.things]

    meta.add_number(5)

    print [t.data for t in meta.things]



Is this a valid approach? Is there a better way to achieve this? Am I crazy for using decorators in this way?

My actual scenario is a PlottedShape with lots of methods like plot, delete, update, select, apply_opacity, apply_color, apply_wireframe, etc. and I also have a PlottedMultiShape made up of multiple shapes (like a snowman shape made of multiple circles).  If PlottedMultiShape.select() is called I want to call select on all the child PlottedShape objects -- and I'd like this same behavior for all of the other methods as well.  Using a decorator seemed like a good "Don't Repeat Yourself" tactic.
Another way to do this would be to use __getattr__().

class Wraps(object):
  def __init__(self, *args):
    self.things = args
  def __getattr__(self, item):
    def each(*args, **kwargs):
      for thing in self.things:
        getattr(thing, item)(*args, **kwargs)
    return each
  def some_real_wraps_method(self):
    print "real method"

class Thing(object):
  def __init__(self, name):
    self.name = name
  def a(self):
    print self.name, "a"
  def b(self, *args, **kwargs):
    print self.name, "b", args, kwargs

w = Wraps(Thing("1"), Thing("2"))
w.a()
w.b()
w.b("arg1", "arg2", named=True)
w.some_real_wraps_method()


Running this will output:

1 a
2 a
1 b () {}
2 b () {}
1 b ('arg1', 'arg2') {'named': True}
2 b ('arg1', 'arg2') {'named': True}
real method


The upside is that you don't have to define an methods in order for them to be proxied. The downside is that it will try to proxy any attribute not defined by Wraps (this can be counteracted with a white list).