473,386 Members | 1,795 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

decorator and API


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

__________________________________________________ _______________
Explore the seven wonders of the world
http://search.msn.com/results.aspx?q...n-US&form=QBRE
Sep 17 '08 #1
5 1121
On Sep 17, 4:56*pm, Lee Harr <miss...@hotmail.comwrote:
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 choiceweights[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.

__________________________________________________ _______________
Explore the seven wonders of the worldhttp://search.msn.com/results.aspx?q=7+wonders+world&mkt=en-US&form=QBRE
What about a function, 'reweight', which wraps the original, and sets
a weight on the wrapper?

def reweight(value):
def reset_weight(method):
#@wraps(method) #optional
def new_method( *ar, **kw ):
return method( *ar, **kw )
new_method.weight = value
return new_method
return reset_weight

Call like this:

class B(A):
action_1= reweight( 50 )( A.action_1 )

You could pass them both in to reweight with two parameters:

class B(A):
action_1= reweight( 50, A.action_1 )

It's about the same. Variable-signature functions have limits.

Otherwise, you can keep dictionaries by name, and checking membership
in them in superclasses that have them by hand. Then you'd need a
consistent name for the dictionary. That option looks like this
(unproduced):

class A:
__weights__= {}
@weight( __weights__, 10 ) ...
@weight( __weights__, 20 ) ...

class B( A ):
__weights__= {} #new instance so you don't change the original
@weight( __weights__, 50 ) ...

B.__weight__ could be an object that knows what it's overriding
(unproduced):

class A:
weights= WeightOb() #just a dictionary, mostly
@weights( 10 ) ...
@weights( 20 ) ...

class B( A ):
weights= WeightOb( A.weights ) #new, refs "super-member"
@weights( 50 ) ...

If either of the last two options look promising, I think I can
produce the WeightOb class. It has a '__call__' method.
Sep 17 '08 #2
On Sep 17, 6:09*pm, "Aaron \"Castironpi\" Brady"
<castiro...@gmail.comwrote:
On Sep 17, 4:56*pm, Lee Harr <miss...@hotmail.comwrote:
I have a class with certain methods from which I want to select
one at random, with weighting.
(snip)
>
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.

class A:
* *weights= WeightOb() #just a dictionary, mostly
* *@weights( 10 ) ...
* *@weights( 20 ) ...

class B( A ):
* *weights= WeightOb( A.weights ) #new, refs "super-member"
* *@weights( 50 ) ...
Lee,

Probably overkill. Here's a solution like above.

class WeightOb( object ):
def __init__( self, *supers ):
self.weights= {}
self.supers= supers
def set( self, weight ):
def __callset__( fun ):
self.weights[ fun.func_name ]= weight
return fun
return __callset__
def reset( self, weight, fun ):
self.weights[ fun.func_name ]= weight
return fun
#search parent 'weight' objects
#return 'child-most' weight of 'name'
def get_weight( self, name ):
if name in self.weights:
return self.weights[ name ]
else:
for x in self.supers:
try:
return x.get_weight( name )
except KeyError: #not found
pass
raise KeyError
#returns a dictionary mapping bound instances to weights
#(hence the second parameter)
def contents( self, inst ):
d= {}
for x in reversed( self.supers ):
d.update( x.contents( inst ) )
d.update( dict( [
( getattr( inst, k ), v ) for k, v in self.weights.iteritems( ) ] ) )
return d
class A( object ):
weights= WeightOb( )
@weights.set( 10 )
def action_1( self ):
print 'action_1'
@weights.set( 20 )
def action_2( self ):
print 'action_2'
#WeightOb.contents needs to know which instance to bind
#functions to. Get weights from an instance that has them.
def getweights( self ):
return self.weights.contents( self )

class B( A ):
weights= WeightOb( A.weights )
action_2= weights.reset( 50, A.action_2 )

a= A()
b= B()
print a.weights.get_weight( 'action_1' )
print a.weights.get_weight( 'action_2' )
print b.weights.get_weight( 'action_1' )
print b.weights.get_weight( 'action_2' )
print a.getweights( )
print b.getweights( )
/Output:

10
20
10
50
{<bound method A.action_2 of <__main__.A object at 0x00A04070>>: 20,
<bound meth
od A.action_1 of <__main__.A object at 0x00A04070>>: 10}
{<bound method B.action_2 of <__main__.B object at 0x00A04090>>: 50,
<bound meth
od B.action_1 of <__main__.B object at 0x00A04090>>: 10}
Sep 17 '08 #3
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 ....
[snip]
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]
weights.pop(0)
actions.pop(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,

--
Steven
Sep 18 '08 #4
On Sep 17, 5:56 pm, Lee Harr <miss...@hotmail.comwrote:
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 choiceweights[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.

Below is a lightweight solution that uses a descriptor. Also the
random action function has been rewritten more efficiently (using
bisect).

George

#======== usage ===========================

class A(object):

# actions don't have to follow a naming convention

@weighted_action(weight=4)
def foo(self):
print "A.foo"

@weighted_action() # default weight=1
def bar(self):
print "A.bar"
class B(A):
# explicit copy of each action with new weight
foo = A.foo.copy(weight=2)
bar = A.bar.copy(weight=4)

@weighted_action(weight=3)
def baz(self):
print "B.baz"

# equivalent to B, but update all weights at once in one statement
class B2(A):
@weighted_action(weight=3)
def baz(self):
print "B2.baz"
update_weights(B2, foo=2, bar=4)
if __name__ == '__main__':
for obj in A,B,B2:
print obj
for action in iter_weighted_actions(obj):
print ' ', action

a = A()
for i in xrange(10): take_random_action(a)
print
b = B()
for i in xrange(12): take_random_action(b)

#====== implementation =======================

class _WeightedActionDescriptor(object):
def __init__(self, func, weight):
self._func = func
self.weight = weight
def __get__(self, obj, objtype):
return self
def __call__(self, *args, **kwds):
return self._func(*args, **kwds)
def copy(self, weight):
return self.__class__(self._func, weight)
def __str__(self):
return 'WeightedAction(%s, weight=%s)' % (self._func,
self.weight)

def weighted_action(weight=1):
return lambda func: _WeightedActionDescriptor(func,weight)

def update_weights(obj, **name2weight):
for name,weight in name2weight.iteritems():
action = getattr(obj,name)
assert isinstance(action,_WeightedActionDescriptor)
setattr(obj, name, action.copy(weight))

def iter_weighted_actions(obj):
return (attr for attr in
(getattr(obj, name) for name in dir(obj))
if isinstance(attr, _WeightedActionDescriptor))

def take_random_action(obj):
from random import random
from bisect import bisect
actions = list(iter_weighted_actions(obj))
weights = [action.weight for action in actions]
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())](obj)
Sep 18 '08 #5
Steven D'Aprano wrote:

I agree with you that the simple explicit approach is better.
Now, to answer the question the OP didn't ask:
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!
It probably doesn't.
Â* Â* assert len(weights) == len(actions)
Â* Â* total = sum(weights)
Â* Â* choice = random.randrange(total)
Â* Â* while choice weights[0]:
Â* Â* Â* Â* choice -= weights[0]
Â* Â* Â* Â* weights.pop(0)
Â* Â* Â* Â* actions.pop(0)
Â* Â* return actions[0]
Assume two actions with equal weights [1, 1]. total becomes 2, and choice is
either 0 or 1, but never weights[0].

While this can be fixed by changing the while condition to

while choice >= weights[0]: #...

I prefer an approach that doesn't destroy the actions and weights lists,
something like

import bisect

def choose_with_weighting(actions, weights=None, acc_weights=None):
if acc_weights is None:
if weights is None:
return random.choice(actions)
else:
sigma = 0
acc_weights = []
for w in weights:
sigma += w
acc_weights.append(sigma)
return actions[bisect.bisect(acc_weights,
random.randrange(acc_weights[-1]))]

especially if you prepare the acc_weights list once outside the function.

Peter

Sep 18 '08 #6

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

11
by: Ville Vainio | last post by:
It might just be that @decorator might not be all that bad. When you look at code that uses it it's not that ugly after all. A lot of the furor about this is probably because it happened so...
41
by: John Marshall | last post by:
How about the following, which I am almost positive has not been suggested: ----- class Klass: def __init__(self, name): self.name = name deco meth0: staticmethod def meth0(x):
17
by: Jim Jewett | last post by:
Guido has said that he is open to considering *one* alternative decorator syntax. At the moment, (Phillip Eby's suggestion) J4 <URL: http://www.python.org/moin/PythonDecorators > (section 5.21...
30
by: Ron_Adam | last post by:
I was having some difficulty figuring out just what was going on with decorators. So after a considerable amount of experimenting I was able to take one apart in a way. It required me to take a...
22
by: Ron_Adam | last post by:
Hi, Thanks again for all the helping me understand the details of decorators. I put together a class to create decorators that could make them a lot easier to use. It still has a few glitches...
3
by: Ron_Adam | last post by:
Ok... it's works! :) So what do you think? Look at the last stacked example, it process the preprocess's first in forward order, then does the postprocess's in reverse order. Which might be...
6
by: Michele Simionato | last post by:
could ildg wrote: > I think decorator is a function which return a function, is this right? > e.g. The decorator below if from http://www.python.org/peps/pep-0318.html#id1. > > def...
5
by: Doug | last post by:
I am looking at using the decorator pattern to create a rudimentary stored proc generator but am unsure about something. For each class that identifies a part of the stored proc, what if I want to...
4
by: thomas.karolski | last post by:
Hi, I would like to create a Decorator metaclass, which automatically turns a class which inherits from the "Decorator" type into a decorator. A decorator in this case, is simply a class which...
8
by: Chris Forone | last post by:
hello group, is there a possibility to implement the decorator-pattern without new/delete (nor smartpt)? if not, how to ensure correct deletion of the objects? thanks & hand, chris
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.