[ Team LiB ] |
13.4 Passing ArgumentsLet'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:
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:
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 ReferencesHere'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:
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 callerIf 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 ChangesIf 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 ParametersWe'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 ] |