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 ....



import random

def weight(value):
    def set_weight(method):
        method.weight = value
        return method
    return set_weight

class A(object):
    def actions(self):
        'return a list of possible actions'

        return [getattr(self, method)
                    for method in dir(self)
                    if method.startswith('action_')]

    def action(self):
        'Select a possible action using weighted choice'

        actions = self.actions()
        weights = [method.weight for method in actions]
        total = sum(weights)

        choice = random.randrange(total)

        while choice> weights[0]:
            choice -= weights[0]
            weights.pop(0)
            actions.pop(0)

        return actions[0]


    @weight(10)
    def action_1(self):
        print "A.action_1"

    @weight(20)
    def action_2(self):
        print "A.action_2"


a = A()
a.action()()




The problem I have now is that if I subclass A and want to
change the weighting of one of the methods, I am not sure
how to do that.

One idea I had was to override the method using the new
weight in the decorator, and then call the original method:

class B(A):
    @weight(50)
    def action_1(self):
        A.action_1(self)


That works, but it feels messy.


Another idea was to store the weightings as a dictionary
on each instance, but I could not see how to update that
from a decorator.

I like the idea of having the weights in a dictionary, so I
am looking for a better API, or a way to re-weight the
methods using a decorator.

Any suggestions appreciated.


Here is another approach:

8<-------------------------------------------------------------------

import random
from bisect import bisect

#by George Sakkis
def take_random_action(obj, actions, weights):
    total = float(sum(weights))
    cum_norm_weights = [0.0]*len(weights)
    for i in xrange(len(weights)):
        cum_norm_weights[i] = cum_norm_weights[i-1] + weights[i]/total
    return actions[bisect(cum_norm_weights, random.random())](obj)

class randomiser(object):

    _cache = []

    @classmethod
    def alert(cls, func):
        assert hasattr(func, 'weight')
        cls._cache.append(func)

    @classmethod
    def register(cls, name, obj):
        actions = {}
        weights = []
        for klass in obj.__class__.__mro__:
            for val in klass.__dict__.itervalues():
                if hasattr(val, '__name__'):
                    key = val.__name__
                    if key in actions:
                        continue
                    elif val in cls._cache:
                        actions[key] = val
                        weights.append(val.weight)
        actions = actions.values()
#setattr(cls, name, classmethod(lambda cls: random.choice(actions)(obj))) setattr(cls, name, classmethod(lambda cls: take_random_action(obj, actions, weights)))

def randomised(weight):
    def wrapper(func):
        func.weight = weight
        randomiser.alert(func)
        return func
    return wrapper

class A(object):

    @randomised(20)
    def foo(self):
        print 'foo'

    @randomised(10)
    def bar(self):
        print 'bar'

class B(A):

    @randomised(50)
    def foo(self):
        print 'foo'

8<-------------------------------------------------------------------

randomiser.register('a', A())
randomiser.register('b', B())
print 'A'
randomiser.a()
randomiser.a()
randomiser.a()
randomiser.a()
randomiser.a()
randomiser.a()
print 'B'
randomiser.b()
randomiser.b()
randomiser.b()
randomiser.b()
randomiser.b()
randomiser.b()


--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to