DekGenius.com
[ Team LiB ] Previous Section Next Section

13.5 Special Argument Matching Modes

Arguments are always passed by assignment in Python; names in the def header are assigned to passed-in objects. On top of this model, though, Python provides additional tools that alter the way the argument objects in the call are matched with argument names in the header prior to assignment. These tools are all optional, but allow you to write functions that support more flexible calling patterns.

By default, arguments are matched by position, from left to right, and you must pass exactly as many arguments as there are argument names in the function header. But you can also specify a match by name, default values, and collectors for extra arguments.

Some of this section gets complicated, and before going into syntactic details, we'd like to stress that these special modes are optional and only have to do with matching objects to names; the underlying passing mechanism is still assignment, after the matching takes place. But here's a synopsis of the available matching modes:


Positionals: matched left to right

The normal case used so far is to match arguments by position.


Keywords: matched by argument name

Callers can specify which argument in the function is to receive a value by using the argument's name in the call, with a name=value syntax.


Varargs: catch unmatched positional or keyword arguments

Functions can use special arguments preceded with * characters to collect arbitrarily extra arguments (much like, and often named for, the varargs feature in the C language, which supports variable-length argument lists).


Defaults: specify values for arguments that aren't passed

Functions may also specify default values for arguments to receive if the call passes too few values, using a name=value syntax.

Table 13-1 summarizes the syntax that invokes the special matching modes.

Table 13-1. Function argument-matching forms

Syntax

Location

Interpretation

func(value)

Caller

Normal argument: matched by position

func(name=value)

Caller

Keyword argument: matched by name

def func(name)

Function

Normal argument: matches any by position or name

def func(name=value)

Function

Default argument value, if not passed in the call

def func(*name)

Function

Matches remaining positional args (in a tuple)

def func(**name)

Function

Matches remaining keyword args (in a dictionary)

In a call (the first two rows of the table), simple names are matched by position, but using the name=value form tells Python to match by name instead; these are called keyword arguments.

In a function header, a simple name is matched by position or name (depending on how the caller passes it), but the name=value form specifies a default value, the *name collects any extra positional arguments in a tuple, and the **name form collects extra keyword arguments in a dictionary.

As a result, special matching modes let you be fairly liberal about how many arguments must be passed to a function. If a function specifies defaults, they are used if you pass too few arguments. If a function uses the varargs (variable argument list) form, you can pass too many arguments; the varargs names collect the extra arguments in a data structure.

13.5.1 Keyword and Default Examples

Python matches names by position by default, like most other languages. For instance, if you define a function that requires three arguments, you must call it with three arguments:

>>> def f(a, b, c): print a, b, c
...

Here, we pass them by position: a is matched to 1, b is matched to 2, and so on:

>>> f(1, 2, 3)
1 2 3

In Python, though, you can be more specific about what goes where when you call a function. Keyword arguments allow us to match by name, instead of position:

>>> f(c=3, b=2, a=1)
1 2 3

Here, the c=3 in the call means match this against the argument named c in the header. The net effect of this call is the same as the prior, but notice that the left to right order no longer matters when keywords are used, because arguments are matched by name, not position. It's even possible to combine positional and keyword arguments in a single call—all positionals are matched first from left to right in the header, before keywords are matched by name:

>>> f(1, c=3, b=2)
1 2 3

When most people see this the first time, they wonder why one would use such a tool. Keywords typically have two roles in Python. First of all, they make your calls a bit more self-documenting (assuming that you use better argument names than a, b, and c). For example, a call of this form:

func(name='Bob', age=40, job='dev')

is much more meaningful than a call with three naked values separated by commas—the keywords serve as labels for the data in the call. The second major use of keywords occurs in conjunction with defaults.

We introduced defaults earlier when discussing nested function scopes. In short, defaults allow us to make selected function arguments optional; if not passed a value, the argument is assigned its default before the function runs. For example, here is a function that requires one argument, and defaults two:

>>> def f(a, b=2, c=3): print a, b, c
...

When called, we must provide a value for a, either by position or keyword; if we don't pass values to b and c, they default to 2 and 3, respectively:

>>> f(1)
1 2 3
>>> f(a=1)
1 2 3

If we pass two values, only c gets its default; with three values, no defaults are used:

>>> f(1, 4)
1 4 3
>>> f(1, 4, 5)
1 4 5

Finally, here is how the keyword and default features interact: because they subvert the normal left-to-right positional mapping, keywords allow us to essentially skip over arguments with defaults:

>>> f(1, c=6)
1 2 6

Here a gets 1 by position, c gets 6 by keyword, and b, in between, defaults to 2.

13.5.2 Arbitrary Arguments Examples

The last two matching extensions, * and **, are designed to support functions that take any number of arguments. The first collects unmatched positional arguments into a tuple:

>>> def f(*args): print args

When this function is called, Python collects all the positional arguments into a new tuple, and assigns the variable args to that tuple. Because it is a normal tuple object, it might be indexed, stepped through with a for loop, and so on:

>>> f(  )
(  )
>>> f(1)
(1,)
>>> f(1,2,3,4)
(1, 2, 3, 4)

The ** feature is similar, but only works for keyword arguments—it collects them into a new dictionary, which can then be processed with normal dictionary tools. In a sense, the ** form allows you to convert from keywords to dictionaries:

>>> def f(**args): print args
...
>>> f(  )
{  }
>>> f(a=1, b=2)
{'a': 1, 'b': 2}

Finally, function headers can combine normal arguments, the *, and the **, to implement wildly flexible call signatures:

>>> def f(a, *pargs, **kargs): print a, pargs, kargs
...
>>> f(1, 2, 3, x=1, y=2)
1 (2, 3) {'y': 2, 'x': 1}

In fact, these features can be combined in more complex ways that almost seem ambiguous at first glance—an idea we will revisit later in this chapter.

13.5.3 Combining Keywords and Defaults

Here is a slightly larger example that demonstrates both keywords and defaults in action. In the following, the caller must always pass at least two arguments (to match spam and eggs), but the other two are optional; if omitted, Python assigns toast and ham to the defaults specified in the header:

def func(spam, eggs, toast=0, ham=0):   # First 2 required
    print (spam, eggs, toast, ham)

func(1, 2)                              # Output: (1, 2, 0, 0)
func(1, ham=1, eggs=0)                  # Output: (1, 0, 0, 1)
func(spam=1, eggs=0)                    # Output: (1, 0, 0, 0)
func(toast=1, eggs=2, spam=3)           # Output: (3, 2, 1, 0)
func(1, 2, 3, 4)                        # Output: (1, 2, 3, 4)

Notice that when keyword arguments are used in the call, the order in which arguments are listed doesn't matter; Python matches by name, not position. The caller must supply values for spam and eggs, but they can be matched by position or name. Also notice that the form name=value means different things in the call and def: a keyword in the call, and a default in the header.

13.5.4 The min Wakeup Call

To make this more concrete, let's work through an exercise that demonstrates a practical application for argument matching tools: suppose you want to code a function that is able to compute the minimum value from an arbitrary set of arguments, and an arbitrary set of object datatypes. That is, the function should accept zero or more arguments—as many as you wish to pass. Moreover, the function should work for all kinds of Python object types—numbers, strings, lists, lists of dictionaries, files, even None.

The first part of this provides a natural example of how the * feature can be put to good use—we can collect arguments into a tuple, and step over each in turn with a simple for loop. The second part of this problem definition is easy: because every object type supports comparisons, we don't have to specialize the function per type (an application of polymorphism); simply compare objects blindly, and let Python perform the correct sort of comparison.

13.5.4.1 Full credit

The following file shows three ways to code this operation, at least one of which was suggested by a student at some point along the way:

  • In the first function, take the first argument (args is a tuple), and traverse the rest by slicing off the first (there's no point in comparing an object to itself, especially if it might be a large structure).

  • The second version lets Python pick off the first and rest of the arguments automatically, and so avoids an index and a slice.

  • The third converts from tuple to list with the built-in tuple call, and employing the list sort method.

Because the sort method is so quick, this third scheme is usually fastest, when this has been timed. File mins.py contains the solution code:

def min1(*args):
    res = args[0]
    for arg in args[1:]:
        if arg < args:
            res = arg
    return res

def min2(first, *rest):
    for arg in rest:
        if arg < first:
            first = arg
    return first

def min3(*args):
    tmp = list(args)
    tmp.sort(  )
    return tmp[0]

print min1(3,4,1,2)
print min2("bb", "aa")
print min3([2,2], [1,1], [3,3])

All three produce the same result, when we run this file. Try typing a few calls interactively to experiment with these on your own.

C:\Python22>python mins.py
2
aa
[1, 1]

Notice that none of these three variants test for the case where no arguments are passed in. They could, but there's no point in doing so here—in all three, Python will automatically raise an exception if no arguments are passed in. The first raises an exception when we try to fetch item 0; the second, when Python detects an argument list mismatch; and the third, when we try to return item 0 at the end.

This is exactly what we want to happen, though—because these functions support any data type, there is no valid sentinel value that we could pass back to designate an error. There are exceptions to this rule (e.g., if you have to run expensive actions before you reach the error); but in general, it's better to assume that arguments will work in your functions' code, and let Python raise errors for you when they do not.

13.5.4.2 Bonus points

Students and readers can get bonus points here for changing these functions to compute the maximum, rather than minimum values. Alas, this one is too easy: the first two versions only require changing < to >, and the third only requires that we return tmp[-1] instead of tmp[0]. For extra points, be sure to set the function name to "max" as well (though this part is strictly optional).

It is possible to generalize a single function like this to compute either a minimum or maximum, by either evaluating comparison expression strings with tools like the eval built-in function to evaluate a dynamically constructed string of code (see the library manual), or by passing in an arbitrary comparison function. File minmax.py shows how to implement the latter scheme:

def minmax(test, *args):
    res = args[0]
    for arg in args[1:]:
        if test(arg, res):
            res = arg
    return res

def lessthan(x, y): return x < y     # see also: lambda
def grtrthan(x, y): return x > y

print minmax(lessthan, 4, 2, 1, 5, 6, 3)
print minmax(grtrthan, 4, 2, 1, 5, 6, 3)

% python minmax.py
1
6

Functions are another kind of object that can be passed into another function like this. To make this a max (or other), we simply pass in the right sort of test function.

13.5.4.3 The punch line

Of course, this is just a coding exercise. There's really no reason to code either min or max functions, because both are built-ins in Python! The built-in versions work almost exactly like ours, but are coded in C for optimal speed.

13.5.5 A More Useful Example: General Set Functions

Here's a more useful example of special argument-matching modes at work. Earlier in the chapter, we wrote a function that returned the intersection of two sequences (it picked out items that appeared in both). Here is a version that intersects an arbitrary number of sequences (1 or more), by using the varargs matching form *args to collect all arguments passed. Because the arguments come in as a tuple, we can process them in a simple for loop. Just for fun, we've also coded an arbitrary-number-arguments union function too; it collects items that appear in any of the operands:

def intersect(*args):
    res = [  ]
    for x in args[0]:                  # Scan first sequence
        for other in args[1:]:         # for all other args.
            if x not in other: break   # Item in each one?
        else:                          # No:  break out of loop
            res.append(x)              # Yes: add items to end
    return res

def union(*args):
    res = [  ]
    for seq in args:                   # For all args
        for x in seq:                  # For all nodes
            if not x in res:
                res.append(x)          # Add new items to result.
    return res

Since these are tools worth reusing (and are too big to retype interactively), we've stored the functions in a module file called inter2.py (more on modules in Part V). In both functions, the arguments passed in at the call come in as the args tuple. As in the original intersect, both work on any kind of sequence. Here they are processing strings, mixed types, and more than two sequences:

% python
>>> from inter2 import intersect, union
>>> s1, s2, s3 = "SPAM", "SCAM", "SLAM"

>>> intersect(s1, s2), union(s1, s2)           # Two operands
(['S', 'A', 'M'], ['S', 'P', 'A', 'M', 'C'])

>>> intersect([1,2,3], (1,4))                  # Mixed types 
[1]

>>> intersect(s1, s2, s3)                      # Three operands
['S', 'A', 'M']

>>> union(s1, s2, s3)
['S', 'P', 'A', 'M', 'C', 'L']

13.5.6 Argument Matching: The Gritty Details

If you choose to use and combine the special argument matching modes, Python will ask you to follow two ordering rules:

  • In a call, all keyword arguments must appear after all non-keyword arguments.

  • In a function header, the *name must appear after normal arguments and defaults, and **name must appear last.

Moreover, Python internally carries out the following steps to match arguments before assignment:

  1. Assign non-keyword arguments by position.

  2. Assign keyword arguments by matching names.

  3. Assign extra non-keyword arguments to *name tuple.

  4. Assign extra keyword arguments to **name dictionary.

  5. Assign default values to unassigned arguments in header.

This is as complicated as it looks, but tracing Python's matching algorithm helps to understand some cases, especially when modes are mixed. We'll postpone additional examples of these special matching modes until we do the exercises at the end of Part IV.

Why You Will Care: Keyword Arguments

Keyword arguments play an important role in Tkinter, the de facto standard GUI API for Python. We meet Tkinter later in this book, but as a preview, keyword arguments set configuration options when GUI components are built. For instance, a call of the form:

from Tkinter import *
widget = Button(text="Press me", command=someFunction)

creates a new button and specifies its text and callback function, using the text and command keyword arguments. Since the number of configuration options for a widget can be large, keyword arguments let you pick and choose. Without them, you might have to either list all possible options by position or hope for a judicious positional argument defaults protocol that handles every possible option arrangement.


As you can see, advanced argument matching modes can be complex. They are also entirely optional; you can get by with just simple positional matching, and it's probably a good idea to do so if you're just starting out. However, because some Python tools make use of them, they're important to know in general.

    [ Team LiB ] Previous Section Next Section