decorator and API

Steven D'Aprano steven at
Thu Sep 18 05:35:52 CEST 2008

On Thu, 18 Sep 2008 02:26:29 +0430, Lee Harr wrote:

> I have a class with certain methods from which I want to select one at
> random, with weighting.
> The way I have done it is this ....


You are coupling the weights, the actions, and the object which chooses 
an action all in the one object. I find that harder to wrap my brain 
around than a more loosely coupled system. Make the chooser independent 
of the things being chosen:

def choose_with_weighting(actions, weights=None):
    if weights is None:
        weights = [1]*len(actions)  # equal weights
    # Taken virtually unchanged from your code.
    # I hope it does what you want it to do!
    assert len(weights) == len(actions)
    total = sum(weights)
    choice = random.randrange(total)
    while choice > weights[0]:
        choice -= weights[0]
    return actions[0]

Loosely couple the actions from their weightings, so you can change them 
independently. Here's a decorator that populates a dictionary with 
weights and actions:

def weight(value, storage):
    def set_weight(method):
        storage[method.__name__] = value
        return method
    return set_weight

Here's how to use it:

class A(object):
    weights = {}
    def __init__(self):
        self.weights = self.__class__.weights.copy()
    @weight(10, weights)
    def action_1(self):
        print "A.action_1"
    @weight(20, weights)
    def action_2(self):
        print "A.action_2"

The class is now populated with a set of default weights, which is then 
copied to the instance. If you want to over-ride a particular weight, you 
don't need to make a subclass, you just change the instance:

obj = A()
obj.weights["action_1"] = 30

method = choose_with_weighting(obj.weights.keys(), obj.weights.values())
getattr(obj, method)() # call the method

Hope this helps,


More information about the Python-list mailing list