decorator and API

Gerard flanagan grflanagan at gmail.com
Thu Sep 18 06:31:45 EDT 2008


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()





More information about the Python-list mailing list