Reputation: 9467
I'd like to mark some methods (class methods in this case, but not necessarily) as related so I can use them together. I don't want to create a hardcoded list of the methods inside all_fruit
because I don't want it to be missed when adding a new type of fruit. Ideally, I'd be able to do something like this:
class Food:
@classmethod
@fruitdecorator
def apples(clss, amount):
return '%d apple(s)' % amount
@classmethod
@fruitdecorator
def oranges(clss, amount):
return '%d orange(s)' % amount
@classmethod
def carrots(clss, amount):
return '%d carrot(s)' % amount
@classmethod
def all_fruit(clss, amount):
pass # somehow return all methods decorated by @fruitdecorator
for method in Food.all_fruit():
print method(Food, 2)
# '2 apples'
# '2 oranges'
I found these decorator answers which let me do the following, which is OK, but I was wondering if it could be simplified at all. In this case I have to define this stuff and call it before, after, and use it in the fruit_baseket class method, which seems a little tedious. (I'm open to non-decorator solutions but they seem like the best way to keep the "mark" and the function definition close together.)
def MakeDecorator():
registry = []
def decorator(fn):
# print dir(fn)
registry.append(fn)
return fn
decorator.all = registry
return decorator
fruitdecorator = MakeDecorator()
class Food:
@classmethod
@fruitdecorator
def apples(clss, amount):
return '%d apple(s)' % amount
@classmethod
@fruitdecorator
def oranges(clss, amount):
return '%d orange(s)' % amount
@classmethod
def carrots(clss, amount):
return '%d carrot(s)' % amount
@classmethod
def fruit_basket(clss, amount):
basket = [xx(clss, amount) for xx in clss.fruit_methods]
return basket
Food.fruit_methods = fruitdecorator.all
print Food.fruit_basket(2)
Upvotes: 3
Views: 1545
Reputation: 1008
It's possible to do this in a more clean, if somewhat more complicated way, using metaclasses. If you have your decorator flag its function in someway (for example, by adding a special attribute), then the metaclass can create a list as a class variable and populate it with the decorated functions (it can tell which have been decorated by looking for the special attribute). A sample implementation would be
class FoodMeta(type):
def __new__(meta, name, bases, dct):
fruit_methods = []
for key, value in dct.items():
if hasattr(value, 'fruity'):
fruit_methods.append(value)
dct['fruit_methods'] = fruit_methods
return super(FoodMeta, meta).__new__(meta, name, bases, dct)
def fruitdecorator(f):
def inner(*args, **kwargs):
# do whatever
pass
inner.fruity = True
return inner
class Food(object):
__metaclass__ = FoodMeta
@fruitdecorator
@classmethod
def apples(clss, amount):
return '%d apple(s)' % amount
@fruitdecorator
@classmethod
def oranges(clss, amount):
return '%d orange(s)' % amount
@classmethod
def carrots(clss, amount):
return '%d carrot(s)' % amount
@classmethod
def fruit_basket(clss, amount):
basket = [xx(clss, amount) for xx in clss.fruit_methods]
return basket
Food.fruit_methods # [Food.apples, Food.oranges]
A crucial thing to note in this solution is that the order of decorators matters: fruitdecorator
must go on the outside (i.e., apply after) classmethod
. This is because the classmethod
decorator doesn't preserve the fruity
attribute on the method, so the metaclass wouldn't know it was registered in the first place.
Upvotes: 1
Reputation: 15310
A decorator takes a function and returns a function. I think your solution is maybe too general, unless you see a need for vegetabledecorator
and saladdecorator
markers, also.
Fruit_methods = []
def fruit(f):
Fruit_methods.append(f)
return f
Or, more generically:
import collections
Food_methods = collections.defaultdict(list)
def food_type(type):
def store(f):
Food_methods[type].append(f)
return f
return store
Upvotes: 1