[Tutor] **kw and self

Remco Gerlich scarblac@pino.selwerd.nl
Wed, 20 Jun 2001 18:55:30 +0200


On  0, Willi Richert <w.richert@gmx.net> wrote:
> as a bloody newbie (who as reverted already some others to Python) I walked 
> 34 times through the whole web and did not get the real meaning of **kw and 
> self.

*breathe in deeply*

Ok.

PART I: THE HEDGEHOG
--------------------

No wait, that's wrong.

PART I, edited: KEYWORD ARGUMENTS
----------------------------------

(this has become quite long. probably the best way to understand it
completely is to enter all the examples into an interpreter and play around
with them).

Do you know what a default argument to a function is? Say we have a function
like

def and_now_for(s = "something completely different"):
   print "And now for...", s

It takes one argument, in principle:
>>> and_now_for("more shrubberies")
And now for... more shrubberies

But you can also take the argument off, so that the default is used:
>>> and_now_for()
And now for... something completely different

This is obviously useful. Now say we have a function that takes several
arguments:

def recipe(ingr1="spam", ingr2="spam", ingr3="eggs", ingr4="spam"):
   print ingr1, ingr1, ing3, "and", ingr4

It works fine when called with no arguments, or with some:
>>> recipe()
spam spam eggs and spam
>>> recipe("beans")
beans spam eggs and spam

But what if we want to leave all the default arguments in, except for the
*last*? You can't give an argument except when you also enter all the ones
before it, or Python doesn't know what you want. Enter keyword arguments:

>>> recipe(ingr4="shrubberies")
spam spam eggs and shrubberies

Again, useful. You can fill in any argument like this. For instance in
Tkinter GUIs, functions have lots and lots of default arguments and you
usually only want to enter one or two, so you use keyword arguments.

Now this can be made even more general, with the **arguments construct.
You don't bother to list the arguments and their defaults anymore, but let
the user give any keyword arguments it wants, and they're passed in as a 
*dictionary*. Consider:

def kwarg_demo(**kwargs):
   print kwargs

>>> kwarg_demo(first="bla", second="whee")
{'first': 'bla', 'second': 'whee'}

The function gets a dictionary, it can see which values were filled in
(using kwargs.has_key or kwargs.keys(), for instance). It's flexible.

That's using **kwargs when you *define* a function. Since 2.0 it can also be
used to pass in values. Say you have a function like

def two_arguments(first, second):
   print "first =", first
   print "second =", second

Now we know that the arguments are called first and second, and we've got
some values for them in a dictionary:

dict = {
   'first': 'silly party',
   'second': 'very silly party'
   }

Now, we can call the function using the dictionary, with
>>> two_arguments(**dict)
first = silly party
second = very silly party

What that actually does is fill in the values from the dictionary, like
two_arguments(first='silly party', second='very silly party')

So both when defining and calling a function, you can use the **arguments
syntax to fake keyword arguments with a dictionary.


(pause. you may reread that a few times. i think i laid out the reasoning
sufficiently)


There's a related syntax, with a single *. This is used for giving a
function a variable number of arguments, that don't need their own names.

Say, we have some functions for adding numbers:

def add2(x,y): return x+y

def add3(x,y,z): return x+y+z

def add4(w,x,y,z): return w+x+y+z

Doesn't really look Pythonic, does it? We need a seperate function for every
amount of numbers. In comes the * argument:

def add(*args):
   sum = 0
   for number in args:
      sum += number
   return sum

Now we can call it with as many numbers as we like!

>>> add(3,4,5,6)
18

The number are passed into the function as a tuple, and we can use a for
loop to go through them. The other way around works as well, since Python 2.0;
you can have any sequence, put a * in front and it passes them as individual
arguments to the function:

>>> l = [1,2,3,4,5]
>>> add(*l)
15
>>> add(5, *l)
20
>>> add(5, *l, 6)
               ^
SyntaxError: invalid syntax

Unfortunately, that one doesn't work. Any normal arguments must always come
before the *args, and those must come before the **kwargs.

So if you want to make a function that takes a function as argument, plus
any number of arguments and also keyword arguments, if any, and calls the
function with it, you do it like this, in Python 2.0+:

def apply(f, *args, **kwargs):
   return f(*args, **kwargs)

In older versions, you can't write this - but this apply() function is a
builtin! And that's how you'd do it in older version, use apply().

>>> apply(add, (3,4,5))  # is equivalent to...
12
>>> add(*(3,4,5))        # in newer versions.

Well, that was quite some text. Have to leave again now, hopefully someone
else writes a tome on 'self' or otherwise I'll do that myself later tonight.

-- 
Remco Gerlich