DekGenius.com
[ Team LiB ] Previous Section Next Section

13.4 Passing Arguments

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 automatically assigning objects to local names. Function arguments are 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 passed-in objects, functions can change passed-in mutable objects, and the result may affect the caller.

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 arguments 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, 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.

13.4.1 Arguments and Shared References

Here's an example that illustrates some of these properties at work:

>>> def changer(x, y):     # Function
...    x = 2               # Changes local name's value only
...    y[0] = 'spam'       # Changes shared object in place
...
>>> X = 1
>>> L = [1, 2]             # Caller
>>> 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. The two assignments within the function are only slightly different in syntax, but have radically different results:

  • Since x is a local name in the function's scope, the first assignment has no effect on the caller—it simply changes local variable x, and does not change the binding of name X in the caller.

  • Argument y is a local name too, but it is passed a mutable object (the list called L in the caller). Since the second assignment is an in-place object change, the result of the assignment to y[0] in the function impacts the value of L after the function returns.

Figure 13-2 illustrates the name/object bindings that exist immediately after the function has been called, and before its code has run.

Figure 13-2. References: arguments share objects with the caller
figs/lpy2_1302.gif

If you recall some of the discussion about shared mutable objects in Chapter 4 and Chapter 7, 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 work like an output of the function.

13.4.2 Avoiding Mutable Argument Changes

If you don't want in-place changes within functions to impact objects you pass to them, simply make explicit copies of mutable objects, as we learned in Chapter 7. For function arguments, we can copy the list at the point of call:

L = [1, 2]
changer(X, L[:])       # Pass a copy, so my L does not change.

We can also copy within the function itself, if we never want to change passed-in objects, regardless of how the function is called:

def changer(x, y):
   y = y[:]            # Copy input list so I don't impact caller.
   x = 2
   y[0] = 'spam'       # Changes my list copy only

Both of these copying schemes don't stop the function from changing the object—they just prevent those changes from impacting the caller. To really prevent changes, we can always convert to immutable objects to force the issue. Tuples, for example, throw an exception when changes are attempted:

L = [1, 2]
changer(X, tuple(L))   # Pass a tuple, so changes are errors.

This scheme uses the built-in tuple function, which builds a new tuple out of all the items in a sequence. It's also something of an extreme—because it forces the function to be written to never change passed in arguments, it might impose more limitation on the function than it should, and should often be avoided. You never know when changing arguments might come in handy for other calls in the future. The function will also lose the ability to call any list-specific methods on the argument, even methods that do not change the object in-place.

13.4.3 Simulating Output Parameters

We've already discussed the return statement, and used it in a few examples. But here's a trick: 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 what some languages label call-by-reference argument passing, we can usually simulate it by returning tuples, and assigning results 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 the code is returning two values here, but it's just one—a two-item tuple, with the optional surrounding parentheses omitted. After the call returns, use tuple assignment to unpack the parts of the returned tuple. (If you've forgotten why, flip back to Section 7.1 in Chapter 7 and Section 8.1 in Chapter 8.)The net effect of this coding pattern is to simulate the output parameters of other languages, by explicit assignments. X and L change after the call, but only because the code said so.

    [ Team LiB ] Previous Section Next Section