>>> from env_helper import info; info()
页面更新时间: 2024-04-04 19:57:01
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

9.2. Function Parameters and Arguments

A function’s parameters are the variable names between the parentheses of the function’s def statement, whereas the arguments are the values between a function call’s parentheses. The more parameters a function has, the more configurable and generalized its code can be. But more parameters also mean greater complexity.

A good rule to adhere to is that zero to three parameters is fine, but more than five or six is probably too many. Once functions become overly complicated, it’s best to consider how to split them into smaller functions with fewer parameters. ## Default Arguments One way to reduce the complexity of your function’s parameters is by pro- viding default arguments for your parameters. A default argument is a value used as an argument if the function call doesn’t specify one. If the majority of function calls use a particular parameter value, we can make that value a default argument to avoid having to enter it repeatedly in the function call.

We specify a default argument in the def statement, following the parameter name and an equal sign. For example, in this introduction() func- tion, a parameter named greeting has the value ‘Hello’ if the function call doesn’t specify it:

>>> def introduction(name, greeting='Hello'):
>>>     print(greeting + ', ' + name)
>>>
>>> introduction('Alice')
Hello, Alice
>>> introduction('Hiro', 'Ohiyo gozaimasu')
Ohiyo gozaimasu, Hiro

When the introduction() function is called without a second argument, it uses the string ‘Hello’ by default. Note that parameters with default argu- ments must always come after parameters without default arguments.

Recall from Chapter 8 that you should avoid using a mutable object, such as an empty list [] or empty dictionary {} , as the default value. “Don’t Use Mutable Values for Default Arguments” on page 143 explains the prob- lem that this approach causes and its solution. ## Using * and ** to Pass Arguments to Functions You can use the * and ** syntax (often pronounced as star and star star) to pass groups of arguments to functions separately. The * syntax allows you to pass in the items in an iterable object (such as a list or tuple). The ** syn- tax allows you to pass in the key-value pairs in a mapping object (such as a dictionary) as individual arguments.

For example, the print() function can take multiple arguments. It places a space in between them by default, as the following code shows:

>>> print('cat', 'dog', 'moose')
cat dog moose

These arguments are called positional arguments, because their posi- tion in the function call determines which argument is assigned to which parameter. But if you stored these strings in a list and tried to pass the list, the print() function would think you were trying to print the list as a single value:

>>> args = ['cat', 'dog', 'moose']
>>> print(args)
['cat', 'dog', 'moose']

Passing the list to print() displays the list, including brackets, quotes, and comma characters. One way to print the individual items in the list would be to split the list into multiple arguments by passing each item’s index to the function indi- vidually, resulting in code that is harder to read:

>>> # An example of less readable code:
>>> args = ['cat', 'dog', 'moose']
>>> print(args[0], args[1], args[2])
cat dog moose

There’s an easier way to pass these items to print() . You can use the *syntax to interpret the items in a list (or any other iterable data type) as individual positional arguments. Enter the following example into the interactive shell.

>>> args = ['cat', 'dog', 'moose']
>>> print(*args)
cat dog moose

The * syntax allows you pass the list items to a function individually, no matter how many items are in the list. You can use the ** syntax to pass mapping data types (such as dictionar- ies) as individual keyword arguments. Keyword arguments are preceded by a parameter name and equal sign. For example, the print() function has a sep keyword argument that specifies a string to put in between the arguments it displays. It’s set to a single space string ’ ’ by default. You can assign a keyword argument to a different value using either an assignment statement or the ** syntax. To see how this works, enter the following into the interactive shell:

>>> print('cat', 'dog', 'moose', sep='-')
cat-dog-moose
>>> kwargsForPrint = {'sep': '-'}
>>> print('cat', 'dog', 'moose', **kwargsForPrint)
cat-dog-moose

Notice that these instructions produce identical output. In the example, we used only one line of code to set up the kwargsForPrint dictionary. But for more complex cases, you might need more code to set up a dictionary of key- word arguments. The ** syntax allows you to create a custom dictionary of configuration settings to pass to a function call. This is useful especially for functions and methods that accept a large number of keyword arguments.

By modifying a list or dictionary at runtime, you can supply a variable number of arguments for a function call using the * and ** syntax. ## Using * to Create Variadic Functions You can also use the * syntax in def statements to create variadic or varargs functions that receive a varying number of positional arguments. For instance, print() is a variadic function, because you can pass any number of strings to it: print(‘Hello!’) or print(‘My name is’, name) , for example. Note that although we used the * syntax in function calls in the previous section, we use the * syntax in function definitions in this section. Let’s look at an example by creating a product() function that takes any number of arguments and multiplies them together:

>>> def product(*args):
>>>
>>>     result = 1
>>>
>>>     for num in args:
>>>
>>>         result *= num
>>>
>>>     return result
>>>
>>> product(3, 3)
9
>>> product(2, 1, 2, 3)
12

Inside the function, args is just a regular Python tuple containing all the positional arguments. Technically, you can name this parameter any- thing, as long as it begins with the star ( * ), but it’s usually named args by convention.

Knowing when to use the * takes some thought. After all, the alterna- tive to making a variadic function is to have a single parameter that accepts a list (or other iterable data type), which contains a varying number of items. This is what the built-in sum() function does:

>>> sum([2, 1, 2, 3])
8

The sum() function expects one iterable argument, so passing it mul- tiple arguments results in an exception:

>>> sum(2, 1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() takes at most 2 arguments (4 given)

Meanwhile, the built-in min() and max() functions, which find the mini- mum or maximum value of several values, accept a single iterable argument or multiple separate arguments:

>>> min([2, 1, 3, 5, 8])
1
>>> min(2, 1, 3, 5, 8)
1
>>> max([2, 1, 3, 5, 8])
8
>>> max(2, 1, 3, 5, 8)
8

All of these functions take a varying number of arguments, so why are their parameters designed differently? And when should we design func- tions to take a single iterable argument or multiple separate arguments using the * syntax?

How we design our parameters depends on how we predict a pro­ grammer will use our code. The print() function takes multiple arguments because programmers more often pass a series of strings, or variables that contain strings, to it, as in print(‘My name is’, name) . It isn’t as common to col- lect these strings into a list over several steps and then pass the list to print() . Also, if you passed a list to print() , the function would print that list value in its entirety, so you can’t use it to print the individual values in the list.

There’s no reason to call sum() with separate arguments because Python already uses the + operator for that. Because you can write code like 2 + 4 + 8 , you don’t need to be able to write code like sum(2, 4, 8) . It makes sense that you must pass the varying number of arguments only as a list to sum() .

The min() and max() functions allow both styles. If the programmer passes one argument, the function assumes it’s a list or tuple of values to inspect. If the programmer passes multiple arguments, it assumes these are the values to inspect. These two functions commonly handle lists of values while the program is running, as in the function call min(allExpenses) . They also deal with separate arguments the programmer selects while writing the code, such as in max(0, someNumber) . Therefore, the functions are designed to accept both kinds of arguments. The following myMinFunction() , which is my own implementation of the min() function, demonstrates this:

>>> def myMinFunction(*args):
>>>     if len(args) == 1:
>>>         values = args[0]
>>>     else:
>>>         values = args
>>>     if len(values) == 0:
>>>         raise ValueError('myMinFunction() args is an empty sequence')
>>>     for i, value in enumerate(values):
>>>         if i == 0 or value < smallestValue:
>>>             smallestValue = value
>>>     return smallestValue

The myMinFunction() uses the * syntax to accept a varying number of arguments as a tuple. If this tuple contains only one value, we assume it’s a sequence of values to inspect 1. Otherwise, we assume that args is a tuple of values to inspect 2. Either way, the values variable will contain a sequence of values for the rest of the code to inspect. Like the actual min() function, we raise ValueError if the caller didn’t pass any arguments or passed an empty sequence 3. The rest of the code loops through values and returns the smallest value found 4. To keep this example simple, myMinFunction() accepts only sequences like lists or tuples rather than any iterable value.

You might wonder why we don’t always write functions to accept both ways of passing a varying number of arguments. The answer is that it’s best to keep your functions as simple as possible. Unless both ways of calling the function are common, choose one over the other. If a function usually deals with a data structure created while the program is running, it’s better to have it accept a single parameter. If a function usually deals with arguments that the programmer specifies while writing the code, it’s better to use the * syn- tax to accept a varying number of arguments. ## Using ** to Create Variadic Functions Variadic functions can use the ** syntax, too. Although the * syntax in def statements represents a varying number of positional arguments, the ** syn- tax represents a varying number of optional keyword arguments.

If you define a function that could take numerous optional keyword arguments without using the ** syntax, your def statement could become unwieldy. Consider a hypothetical formMolecule() function, which has param- eters for all 118 known elements:

def formMolecule(hydrogen, helium, lithium, beryllium, boron, --snip--

Passing 2 for the hydrogen parameter and 1 for the oxygen parameter to return ‘water’ would also be burdensome and unreadable, because you’d have to set all of the irrelevant elements to zero:

>>> formMolecule(2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 --snip--
'water'

You could make the function more manageable by using named key- word parameters that each have a default argument, freeing you from hav- ing to pass that parameter an argument in a function call.

NOTE

Although the terms argument and parameter are well defined, programmers tend to use keyword argument and keyword parameter interchangeably.

For example, this def statement has default arguments of 0 for each of the keyword parameters:

>>> def formMolecule(hydrogen=0, helium=0, lithium=0, beryllium=0, --snip--

This makes calling formMolecule() easier, because you only need to spec- ify arguments for parameters that have a different value than the default argument. You can also specify the keyword arguments in any order:

>>> formMolecule(hydrogen=2, oxygen=1)
'water'
>>> formMolecule(oxygen=1, hydrogen=2)
'water'
>>> formMolecule(carbon=8, hydrogen=10, nitrogen=4, oxygen=2)
'caffeine'

But you still have an unwieldy def statement with 118 parameter names. And what if new elements were discovered? You’d have to update the function’s def statement along with any documentation of the function’s parameters.

Instead, you can collect all the parameters and their arguments as key-value pairs in a dictionary using the ** syntax for keyword arguments. Technically, you can name the ** parameter anything, but it’s usually named kwargs by convention:

>>> def formMolecules(**kwargs):
>>>
>>>     if len(kwargs) == 2 and kwargs['hydrogen'] == 2 and kwargs['oxygen'] == 1:
>>>
>>>         return 'water'
>>>
>>> # (rest of code for the function goes here)
>>>
>>> formMolecules(hydrogen=2, oxygen=1)
'water'

The ** syntax indicates that the kwargs parameter can handle all key- word arguments passed in a function call. They’ll be stored as key-value pairs in a dictionary assigned to the kwargs parameter. As new chemical ele- ments are discovered, you’d need to update the function’s code but not its def statement, because all keyword arguments are put into kwargs :

>>> def formMolecules(**kwargs):
>>>
>>>     if len(kwargs) == 1 and kwargs.get('unobtanium') == 12:
>>>
>>>         return 'aether'
>>>
>>> # (rest of code for the function goes here)
>>>
>>> formMolecules(unobtanium=12)
'aether'

As you can see, the def statement 1 is the same as before, and only the function’s code 2 needed updating. When you use the ** syntax, the def statement and the function calls become much simpler to write and still produce readable code. ## Using * and ** to Create Wrapper Functions A common use case for the * and ** syntax in def statements is to create wrapper functions, which pass on arguments to another function and return that function’s return value. You can use the * and ** syntax to for- ward any and all arguments to the wrapped function. For example, we can create a printLowercase() function that wraps the built-in print() function. It relies on print() to do the real work but converts the string arguments to lowercase first:

>>> def printLower(*args, **kwargs):
>>>
>>>     args = list(args)
>>>
>>>     for i, value in enumerate(args):
>>>
>>>         args[i] = str(value).lower()
>>>
>>>     return print(*args, **kwargs)
>>> name = 'Albert'
>>> printLower('Hello,', name)
hello, albert
>>> printLower('DOG', 'CAT', 'MOOSE', sep=', ')
dog, cat, moose

The printLower() function 1 uses the * syntax to accept a varying number of positional arguments in a tuple assigned to the args parameter, whereas the ** syntax assigns any keyword arguments to a dictionary in the kwargs parameter. If a function uses args and **kwargs together, theargs parameter must come before the **kwargs parameter. We pass these on to the wrapped print() function, but first our function modifies some of the arguments, so we create a list form of the args tuple 2.

After changing the strings in args to lowercase, we pass the items in args and key-value pairs in kwargs as separate arguments to print() using the * and ** syntax 3. The return value of print() also gets returned as the return value of printLower() . These steps effectively wrap the print() function.