[ Team LiB ] |
13.5 Special Argument Matching ModesArguments 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:
Table 13-1 summarizes the syntax that invokes the special matching modes.
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 ExamplesPython 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 ExamplesThe 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 DefaultsHere 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 CallTo 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 creditThe 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:
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 pointsStudents 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 lineOf 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 FunctionsHere'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 DetailsIf you choose to use and combine the special argument matching modes, Python will ask you to follow two ordering rules:
Moreover, Python internally carries out the following steps to match arguments before assignment:
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.
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 ] |