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.
Thing
andMetaThing
in the first place. It's a "MetaThing
containsThing
s" relationship, not a "MetaThing
is aThing
" relationship. I can't say for certain what is appropriate, though, sinceThing
is a purely hypothetical example. \$\endgroup\$for word in phrase: word.capitalize()
.... and do this also for bold(), underline(), etc. I see what you mean though... having ThingCollection which owns a list of Things would be more explicit (Composition instead of Inheritance I think?) and could still handle the method proxy/broadcasting behavior. \$\endgroup\$