Let’s expand on the notion of argument passing in Python. Earlier, we noted that arguments are passed by assignment ; this has a few ramifications that aren’t always obvious to beginners:
- Arguments are passed by assigning objects to local names
Function arguments should be familiar territory by now: they’re just another instance of Python assignment at work. Function arguments are references to (possibly) shared objects referenced by the caller.
- Assigning to argument names inside a function doesn’t affect the caller
Argument names in the function header become new, local names when the function runs, in the scope of the function. There is no aliasing between function argument names and names in the caller.
- Changing a mutable object argument in a function may impact the caller
On the other hand, since arguments are simply assigned to objects, functions can change passed-in mutable objects, and the result may affect the caller.
Here’s an example that illustrates some of these properties at work:
>>>def changer(x, y):
...x = 2
# changes local name's value only ...y[0] = 'spam'
# changes shared object in place...
>>>X = 1
>>>L = [1, 2]
>>>changer(X, L)
# pass immutable and mutable >>>X, L
# X unchanged, L is different (1, ['spam', 2])
In this code, the changer
function assigns to
argument name x
and a component in the object
referenced by argument y
. Since
x
is a local name in the function’s scope,
the first assignment has no effect on the caller; it doesn’t
change the binding of name X
in the caller.
Argument y
is a local name too, but it’s
passed a mutable object (the list called L
in the
caller); the result of the assignment to y[0]
in
the function impacts the value of L
after the
function returns. Figure 4.2 illustrates the
name/object bindings that exist immediately after the function is
called.
If you recall some of the discussion about shared mutable objects in
Chapter 2, you’ll recognize that this is the
exact same phenomenon at work: changing a mutable object in place can
impact other references to the object. Here, its effect is to make
one of the arguments an output of the function.
(To avoid this, type y
=
y[:]
to make a copy.)
Python’s pass-by-assignment scheme isn’t the same as C++’s reference parameters, but it turns out to be very similar to C’s in practice:
- Immutable arguments act like C’s “by value” mode
Objects such as integers and strings are passed by object reference (assignment), but since you can’t change immutable objects in place anyhow, the effect is much like making a copy.
- Mutable arguments act like C’s “by pointer” mode
Objects such as lists and dictionaries are passed by object reference too, which is similar to the way C passes arrays as pointers—mutable objects can be changed in place in the function, much like C arrays.
Of course, if you’ve never used C, Python’s argument-passing mode will be simpler still; it’s just an assignment of objects to names, which works the same whether the objects are mutable or not.
We’ve already discussed the
return
statement, and used it in a few examples. But here’s a trick we
haven’t shown yet: because return
sends back
any sort of object, it can return multiple
values, by packaging them in a tuple. In fact, although Python
doesn’t have call by reference, we can
simulate it by returning tuples and assigning back to the original
argument names in the caller:
>>>def multiple(x, y):
...x = 2
# changes local names only ...y = [3, 4]
...return x, y
# return new values in a tuple...
>>>X = 1
>>>L = [1, 2]
>>>X, L = multiple(X, L)
# assign results to caller's names >>>X, L
(2, [3, 4])
It looks like we’re returning two values here, but it’s just one—a two-item tuple, with the surrounding parentheses omitted. If you’ve forgotten why, flip back to the discussion of tuples in Chapter 2.
Although arguments are always passed by assignment, Python provides additional tools that alter the way the argument objects in the call are paired with argument names in the header. By default, they 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 we get 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 as an introduction, here’s a synopsis of the available matching modes:
- Positionals: matched left to right
The normal case which we’ve 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.
- varargs: catch unmatched positional or keyword arguments
Functions can use special arguments to collect arbitrarily many extra arguments (much as the
varargs
feature in C, 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
Table 4.2 summarizes the syntax that specify the special matching modes.
Table 4-2. Function Argument-Matching Forms
Syntax |
Location |
Interpretation |
---|---|---|
|
Caller |
Normal argument: matched by position |
|
Caller |
Keyword argument: matched by name |
|
Function |
Normal argument: matches any by position or name |
|
Function |
Default argument value, if not passed in the call |
|
Function |
Matches remaining positional args (in a tuple) |
|
Function |
dictionary) |
In the caller (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 the 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
forms, you can pass too
many arguments; the varargs
names collect the
extra arguments in a data structure.
Let’s look at an example that demonstrates
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.
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 which 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 way too big to retype
interactively), we’ve stored our functions in a module file
called inter2.py here (more on modules in Chapter 5). 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)
# 2 operands (['S', 'A', 'M'], ['S', 'P', 'A', 'M', 'C']) >>>intersect([1,2,3], (1,4))
# mixed types [1] >>>intersect(s1, s2, s3)
# 3 operands ['S', 'A', 'M'] >>>union(s1, s2, s3)
['S', 'P', 'A', 'M', 'C', 'L']
If you choose to use and combine the special matching modes, Python has two ordering rules:
In the call, keyword arguments must appear after all nonkeyword arguments.
In a function header, the *
name
must be after normal arguments and defaults, and**name
must be last.
Moreover, Python internally carries out the following steps to match arguments before assignment:
Assign nonkeyword arguments by position
Assign keyword arguments by matching names
Assign extra nonkeyword arguments to
*name
tupleAssign extra keyword arguments to
**name
dictionaryAssign 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 this chapter.
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, some Python tools make us e of them, so they’re important to know.
Get Learning Python now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.