预计阅读本页时间:-
Arbitrary Arguments Examples
The last two matching extensions, * and **, are designed to support functions that take any number of arguments. Both can appear in either the function definition or a function call, and they have related purposes in the two locations.
Collecting arguments
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
The first use, in the function definition, 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 can 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 it 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, which you can then step through with keys calls, dictionary iterators, and the like:
>>> 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. For instance, in the following, 1 is passed to a by position, 2 and 3 are collected into the pargs positional tuple, and x and y wind up in the kargs keyword dictionary:
>>> 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 even more complex ways that may seem ambiguous at first glance—an idea we will revisit later in this chapter. First, though, let’s see what happens when * and ** are coded in function calls instead of definitions.
Unpacking arguments
In recent Python releases, we can use the * syntax when we call a function, too. In this context, its meaning is the inverse of its meaning in the function definition—it unpacks a collection of arguments, rather than building a collection of arguments. For example, we can pass four arguments to a function in a tuple and let Python unpack them into individual arguments:
>>> def func(a, b, c, d): print(a, b, c, d)
...
>>> args = (1, 2)
>>> args += (3, 4)
>>> func(*args)
1 2 3 4
Similarly, the ** syntax in a function call unpacks a dictionary of key/value pairs into separate keyword arguments:
>>> args = {'a': 1, 'b': 2, 'c': 3}
>>> args['d'] = 4
>>> func(**args)
1 2 3 4
Again, we can combine normal, positional, and keyword arguments in the call in very flexible ways:
>>> func(*(1, 2), **{'d': 4, 'c': 4})
1 2 4 4
>>> func(1, *(2, 3), **{'d': 4})
1 2 3 4
>>> func(1, c=3, *(2,), **{'d': 4})
1 2 3 4
>>> func(1, *(2, 3), d=4)
1 2 3 4
>>> f(1, *(2,), c=3, **{'d':4})
1 2 3 4
This sort of code is convenient when you cannot predict the number of arguments that will be passed to a function when you write your script; you can build up a collection of arguments at runtime instead and call the function generically this way. Again, don’t confuse the */** syntax in the function header and the function call—in the header it collects any number of arguments, while in the call it unpacks any number of arguments.
Note
As we saw in Chapter 14, the *pargs form in a call is an iteration context, so technically it accepts any iterable object, not just tuples or other sequences as shown in the examples here. For instance, a file object works after the *, and unpacks its lines into individual arguments (e.g., func(*open('fname')).
This generality is supported in both Python 3.0 and 2.6, but it holds true only for calls—a *pargs in a call allows any iterable, but the same form in a def header always bundles extra arguments into a tuple. This header behavior is similar in spirit and syntax to the * in Python 3.0 extended sequence unpacking assignment forms we met in Chapter 11 (e.g., x, *y = z), though that feature always creates lists, not tuples.
Applying functions generically
The prior section’s examples may seem obtuse, but they are used more often than you might expect. Some programs need to call arbitrary functions in a generic fashion, without knowing their names or arguments ahead of time. In fact, the real power of the special “varargs” call syntax is that you don’t need to know how many arguments a function call requires before you write a script. For example, you can use if logic to select from a set of functions and argument lists, and call any of them generically:
if <test>:
action, args = func1, (1,) # Call func1 with 1 arg in this case
else:
action, args = func2, (1, 2, 3) # Call func2 with 3 args here
...
action(*args) # Dispatch generically
More generally, this varargs call syntax is useful any time you cannot predict the arguments list. If your user selects an arbitrary function via a user interface, for instance, you may be unable to hardcode a function call when writing your script. To work around this, simply build up the arguments list with sequence operations, and call it with starred names to unpack the arguments:
>>> args = (2,3)
>>> args += (4,)
>>> args
(2, 3, 4)
>>> func(*args)
Because the arguments list is passed in as a tuple here, the program can build it at runtime. This technique also comes in handy for functions that test or time other functions. For instance, in the following code we support any function with any arguments by passing along whatever arguments were sent in:
def tracer(func, *pargs, **kargs): # Accept arbitrary arguments
print('calling:', func.__name__)
return func(*pargs, **kargs) # Pass along arbitrary arguments
def func(a, b, c, d):
return a + b + c + d
print(tracer(func, 1, 2, c=3, d=4))
When this code is run, arguments are collected by the tracer and then propagated with varargs call syntax:
calling: func
10
We’ll see larger examples of such roles later in this book; see especially the sequence timing example in Chapter 20 and the various decorator tools we will code in Chapter 38.
The defunct apply built-in (Python 2.6)
Prior to Python 3.0, the effect of the *args and **args varargs call syntax could be achieved with a built-in function named apply. This original technique has been removed in 3.0 because it is now redundant (3.0 cleans up many such dusty tools that have been subsumed over the years). It’s still available in Python 2.6, though, and you may come across it in older 2.X code.
In short, the following are equivalent prior to Python 3.0:
func(*pargs, **kargs) # Newer call syntax: func(*sequence, **dict)
apply(func, pargs, kargs) # Defunct built-in: apply(func, sequence, dict)
For example, consider the following function, which accepts any number of positional or keyword arguments:
>>> def echo(*args, **kwargs): print(args, kwargs)
...
>>> echo(1, 2, a=3, b=4)
(1, 2) {'a': 3, 'b': 4}
In Python 2.6, we can call it generically with apply, or with the call syntax that is now required in 3.0:
>>> pargs = (1, 2)
>>> kargs = {'a':3, 'b':4}
>>> apply(echo, pargs, kargs)
(1, 2) {'a': 3, 'b': 4}
>>> echo(*pargs, **kargs)
(1, 2) {'a': 3, 'b': 4}
The unpacking call syntax form is newer than the apply function, is preferred in general, and is required in 3.0. Apart from its symmetry with the *pargs and **kargs collector forms in def headers, and the fact that it requires fewer keystrokes overall, the newer call syntax also allows us to pass along additional arguments without having to manually extend argument sequences or dictionaries:
>>> echo(0, c=5, *pargs, **kargs) # Normal, keyword, *sequence, **dictionary
(0, 1, 2) {'a': 3, 'c': 5, 'b': 4}
That is, the call syntax form is more general. Since it’s required in 3.0, you should now disavow all knowledge of apply (unless, of course, it appears in 2.X code you must use or maintain...).