Advanced Parameter Handling For Functions
In the section called “More Features” we hinted that Python
functions can handle a variable number of argument values in addition to
supporting optional argument values. Earlier, when we defined a function
that had optional parameters, it had a definite number of parameters,
but some (or all) could be omitted because we provided default values
for them.
If we provide too many positional parameters to a function, Python
raises an exception. Consider the following example. We defined a
function of three positional parameters, and then evaluated it with more
than three argument values.
>>>
def avg(a,b,c):
return (a+b+c)/3.0
>>>
avg(10,11,12)
11.0
>>>
avg(10,11,12,13,14,15,16)
Traceback (most recent call last):
File "<pyshell#16>", line 1, in -toplevel-
avg(10,11,12,13,14,15,16)
TypeError: avg() takes exactly 3 arguments (7 given)
Unlimited Number of Positional Argument Values
Python lets us define a function that handles an unknown and
unlimited number of argument values. Examples of built-in functions
with a unlimited number of argument values are
max
and min
.
Rather than have Python raise an exception, we can request the
additional positional argument values be collected into a
tuple
. To do this, you provide a final
parameter definition of the form
*
extras
. The *
indicates that this parameter variable is the place to capture the
extra argument values. The variable, here called
extras
, will receive a sequence with all of
the extra positional argument values.
You can only provide one such variable (if you provided two, how
could Python decide which of these two got the extra argument values?)
You must provide this variable after the ordinary positional
parameters in the function definition.
Examples of Unlimited Positional Arguments. The following function accepts an unlimited number of
positional arguments; it collects these in a single
tuple
parameter,
args
.
def myMax( *args ):
max= arg[0]
for a in args[1:]:
if a > max: max= a
return max
We take the first element of the args
tuple
as our current guess at the maximum
value, max
. We use a
for
loop
that will set the variable a
to each of the other
arguments. If a
is larger than our current guess,
we update the current guess, max
. At the end of the
loop, the post condition is that we have visited every element in the
list
args
; the current guess
must be the largest value.
Here's another example. In this case we have a fixed parameter
in the first position and all the extra parameters collected into a
tuple
called vals
.
def printf( format, *vals ):
print format % vals
This should look familiar to C programmers. Now we can write the
following, which may help ease the transition from C to Python.
printf( "%s = %d", "some string", 2 )
printf( "%s, %s, %d %d", "thing1", "thing2", 3, 22 )
Unlimited Number of Keyword Argument Values
In addition to collecting extra positional argument values into
a single parameter, Python can also collect extra keyword argument
values into a dict
. If you want a container of
keyword arguments, you provide a parameter of the form
**
extras
. Your variable, here
called
extras
, will receive a
dict
with all of the keyword parameters.
The following function accepts any number opf keyword arguments;
they are collected into a single parameter.
def rtd( **args ):
if args.has_key( "rate" ) and args.has_key( "time" ):
args["distance"]= args["rate"]*args["time"]
elif args.has_key( "rate" ) and args.has_key( "distance" ):
args["time"]= args["distance"]/args["rate"]
elif args.has_key( "time" ) and args.has_key( "distance" ):
args["rate"]= args["distance"]/args["time"]
else:
raise Exception("%r does not compute" % ( args, ) )
return args
Here's two examples of using this rtd
function.
>>>
print rtd( rate=60, time=.75 )
{'distance': 45.0, 'rate': 60.0, 'time': 0.75}
>>>
print rtd( distance=173, time=2+50/60.0 )
{'distance': 173, 'rate': 61.058823529411761,
'time': 2.8333333333333335}
The keyword arguments are collected into a
dict
, named args
. We check
for some combination of "rate", "time" and "distance" by using the
dict
method has_key
.
If the dict
of keyword arguments,
args
, has the given keyword,
has_key
returns true. For each combination,
we can solve for the remaining value and update the
dict
by insert the additional key and value
into the dict
.
Using a Container Instead of Individual Arguments
We can also force a sequence to be broken down into individual
parameters. We can use a special version of the
*
operator when evaluating a function. Here's an example of forcing a
3-tuple
to be assigned to three positional
parameters.
>>>
def avg3(a,b,c):
... return (a+b+c)/3.0
>>>
avg3( *(4,5,7) )
5.333333333333333
In this example, we told Python to break down our
3-tuple
, and assign each value of the
tuple
to a separate parameter variable.
As with the
*
operator, we can use
**
to make a dict
become a
series of keyword parameters to a function.
>>> d={ 'a':5, 'b':6, 'c':9 }
>>> avg3( **d )
6.666666666666667
In this example, we told Python to assign each element of the
dict
, d
, to a parameter of
our function.
We can mix and match this with ordinary parameter assignment,
also. Here's an example.
>>> avg3( 2, b=3, **{'c':4} )
3.0
Here we've called our function with three argument values. The
parameter a
will get its value from a simple
positional parameter. The parameter b
will get its
value from a keyword argument. The parameter c
will
get its value from having the dict
{'c':4}
turned into keyword parameter
assignment.
We'll make more use of this in the section called “Inheritance”.
Creating a
print
function
The
print
statement has irregular, complex
syntax. Also, because it's a statement, making blanket changes
requires tedious, manual search and replace on the program source.
Writing a print function can make the
print
statement slightly easier to cope with.
We want to mirror the capabilities of the existing print
statement, so we need to accept an unlimited number of positional
parameters, as well as some optional keyword parameters. One keyword
parameter can be used in place of the "chevron" feature to define the
output file. The other keyword parameters can provide formatting
information: what character goes between fields and what character (if
any) goes at the end of the line.
We want to have a syntax summary like this.
-
_print
(
args
,
sep=
string
,
end=
string
,
file=
file
)
-
Converts the argument values to strings, and writes them
to the given file. If no file is provided, writes to standard
output. The
sep
parameter is the column
separator, which is a space by default. The
end
parameter is the end-of-line
character, which is a \n
by default.
A simple version of our print replacement function would look
something like the following. This example looks forward to using the
map
function, which we won't look at in detail
until the section called “Sequence Processing Functions: map
,
filter
, reduce
and
zip
”.
Example 15.1. printFunction.py
import sys
def _print( *args, **kw ):
out= kw.get('file',sys.stdout)
linesep= kw.get('end','\n')
colsep= kw.get('sep',' ')
out.write( colsep.join( map(str,args) ) )
out.write( linesep )
Our intent is to have a function that we can use as follows. We
want to provide a long sequence of positional parameters first,
followed by some optional keyword parameters.
_print( "Python Version", end="" )
_print( sys.version )
_print( "This is", "Stderr", out=sys.stderr, )
_print( "This is", "Stdout", out=sys.stdout, )
Here are the original
print
statements that
we replaced.
print "Python Version",
print sys.version
print >>sys.stderr, "This is", "Stderr"
prinr >>sys.stdout, "This is", "Stdout"
Note that we can't simply say def _print ( *args, sep=' ',
end='\n', file=None )
as the definition of this function. We
can't have the "all extra positional parameters" listed first in the
function definition, even though this is our obvious intent. To
achieve the syntax we want, we have to write a function which collects
all positional parameters into one sequence of values, and all keyword
parameters into a separate dictionary.