Learning Python - Mark Lutz [226]
Of course, this polymorphic model of programming means we have to test our code to detect errors, rather than providing type declarations a compiler can use to detect some types of errors for us ahead of time. In exchange for an initial bit of testing, though, we radically reduce the amount of code we have to write and radically increase our code’s flexibility. As you’ll learn, it’s a net win in practice.
A Second Example: Intersecting Sequences
Let’s look at a second function example that does something a bit more useful than multiplying arguments and further illustrates function basics.
In Chapter 13, we coded a for loop that collected items held in common in two strings. We noted there that the code wasn’t as useful as it could be because it was set up to work only on specific variables and could not be rerun later. Of course, we could copy the code and paste it into each place where it needs to be run, but this solution is neither good nor general—we’d still have to edit each copy to support different sequence names, and changing the algorithm would then require changing multiple copies.
Definition
By now, you can probably guess that the solution to this dilemma is to package the for loop inside a function. Doing so offers a number of advantages:
Putting the code in a function makes it a tool that you can run as many times as you like.
Because callers can pass in arbitrary arguments, functions are general enough to work on any two sequences (or other iterables) you wish to intersect.
When the logic is packaged in a function, you only have to change code in one place if you ever need to change the way the intersection works.
Coding the function in a module file means it can be imported and reused by any program run on your machine.
In effect, wrapping the code in a function makes it a general intersection utility:
def intersect(seq1, seq2):
res = [] # Start empty
for x in seq1: # Scan seq1
if x in seq2: # Common item?
res.append(x) # Add to end
return res
The transformation from the simple code of Chapter 13 to this function is straightforward; we’ve just nested the original logic under a def header and made the objects on which it operates passed-in parameter names. Because this function computes a result, we’ve also added a return statement to send a result object back to the caller.
Calls
Before you can call a function, you have to make it. To do this, run its def statement, either by typing it interactively or by coding it in a module file and importing the file. Once you’ve run the def, you can call the function by passing any two sequence objects in parentheses:
>>> s1 = "SPAM"
>>> s2 = "SCAM"
>>> intersect(s1, s2) # Strings
['S', 'A', 'M']
Here, we’ve passed in two strings, and we get back a list containing the characters in common. The algorithm the function uses is simple: “for every item in the first argument, if that item is also in the second argument, append the item to the result.” It’s a little shorter to say that in Python than in English, but it works out the same.
To be fair, our intersect function is fairly slow (it executes nested loops), isn’t really mathematical intersection (there may be duplicates in the result), and isn’t required at all (as we’ve seen, Python’s set data type provides a built-in intersection operation). Indeed, the function could be replaced with a single list comprehension expression, as it exhibits the classic loop collector code pattern:
>>> [x for x in s1 if x in s2]
['S', 'A', 'M']
As a function basics example, though, it does the job—this single piece of code can apply to an entire range of object types, as the next section explains.
Polymorphism Revisited
Like all functions in Python, intersect is polymorphic. That is,