(You top-posted, which confuses the sequence of message text. So I clipped it off and posted my message at the bottom, which is the convention on this newsgroup)

Vincent Davis wrote:
DaveA posted
import random, functools

class Person:
    def __init__(self, size):
        self.size = size

        def __str__(self):
            return "Person of size %s" % self.size

class MakePeople:
    def __init__(self, random_func):
        self.random_func = random_func

    def make_them(self, count):
        return [Person(self.random_func()) for i in xrange(count)]

people_maker = MakePeople(functools.partial(random.gauss, 100, 2))
persons = people_maker.make_them(100)
for person in persons:
    print person.size

I changed the last line, from
print person
print person.size

So this does what I want, but I am not sure why.
I read the entry about functools.partial but it was not very clear to me.
If I
people_maker = MakePeople(random.gauss(100, 2))
then I only get 1 random #.
and if I
MakePeople('random.gauss(100, 2)')
then I just a fix string
So DaveA uses

functools.partial(random.gauss, 100, 2)

not obvious to me from that it should not be

functools.partial(random.gauss(100, 2))

and I guess the other key is

Person(self.random_func())

Also now this
people_maker = MakePeople(123)

does not work, which is not terrible.

Anyone have some more to add, I would not have confidence in applying this
to new situations and it seems.

Also I thank DaveA improving my Python conventions. I am really bad about
that. Is there a cheat sheet for Python conventions.

Like class (Capitals), def (two_words), I guess I should make my own.

Thanks
Vincent Davis
720-301-3003

<snip>

For the official Python style guide, see http://www.python.org/dev/peps/pep-0008/

There are a couple of things going on in my code sample, and I'll try to elaborate on them. I'm not claiming it's the 'right' answer, just that it satisfies what I think were the most important goals you had. But if you want to save the list in the MakePeople() instance, you'd add an additional parameter to its constructor, and combine the second method into __init__(). But as someone else points out, at that point, it's then hardly worth making a class out of it.

First the tough part.  In your original code, your caller was doing:


listofp = makepeople(random.guass(100, 2))

But that passes only a single value into the makepeople constructor.  If you're 
always going to use gaussian, then you can just move the function call into the 
make_them() loop.  But if you want to do the same thing, but with a different 
distribution, you need to pass a function object instead.  Learning about 
function objects is very useful.  You should really play with them in a simpler 
situation, to get an idea how they work.

At its simplest, a function object is just the function name, without 
parentheses.  You can store it (and pass it as a parameter to another function) 
just like any other object.  And then when you actually want to call it, you 
can use the new name with parentheses, kind of like an alias to the original 
function name.

import math

def indirection(funcobject, argument):
   return funcobject(math.pi/180 * argument)

print indirection(math.sin, 30)
print indirection(math.cos, 30)


Now what happens here? The indirection() function calls an entirely different function the two times it's called, one time it calls sin, and the other time it calls cos. As long as all the arguments to the function are going to be supplied here, there's no confusion. Try the same thing with any other set of functions, given that all of them take the same number and types of arguments.

This opens the door to all sorts of things, such as a mini-calculator (following is mostly pseudo-code):

funcname =  getnamefrom user()
angleinradians = getnumberfromuser()

converter_dict = { "sin": math.sin,  "cos":math.cos }
print  converter_dict[funcname](angleinradians)

So the values of the dictionary are actual function objects, ready to be called.

What happens if some of the function parameters are known to the caller, and not to the callee? I'll use the random.gauss example again. If we want the caller to be able to specify them, we could do something like the following:

def print_indirection(funcobject, arg1, arg2, count):
   for i in xrange(count):
       print funcobject(arg1, arg2)

and call it:
   print_indirection(random.gauss, 100, 2, 44)

to get 44 values with a gaussian distributon. But now suppose we wanted to be able to use the same function with random.getrandbits() ? That function only takes a single parameter, so the print funcobject() would blow up.

functools.partial lets us bind a function object with some or all of its arguments already attached. So in our case, the caller (who knows what the arguments look like) gets to lay them out, without being stuck with any particular ones.

So, we rewrite print_indirection something like this:

def print_indirection(funcobject, count):
   for i in xrange(count):
       print funcobject()

and call it like:
   print_indirection(functools.partial(random.gauss, 100, 2), 44)
   print_indirection(functools.partial(random.getrandbits, 17),    44)

The key here is that
    functools.partial(random.gauss, 100, 2)
is a function object that takes no parameters, because the 2 parameters of the underlying function have already been bound.

Now this can be generalized a bit, but I hope you understand it better than before. You have to play with it. Make lists or dicts of your own functions, with or without binding parameters. The key is that such a list should have uniform signatures, which in the examples I've shown means none of the function objects take any (more) arguments.

DaveA

_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor

Reply via email to