[Tutor] class attribute to initiate more classes
Dave Angel
davea at ieee.org
Sat Oct 31 16:47:31 CET 2009
(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
More information about the Tutor
mailing list