• No results found

Additional Behaviors

In document OO Programming in Python (Page 59-67)

Getting Started in Python

2.2 Using Objects: the list Class

2.2.6 Additional Behaviors

We have been exploring a list as a representative example of an object that can be manip-ulated, and to demonstrate a typical syntax for such interactions. We have seen how to construct instances, to call methods and communicate with parameters and return values, and to invoke common behaviors using operator shorthands. These lessons carry over to many other classes that we will see.

Of course lists are a valuable tool for Python programmers and there are many more behaviors we have yet to discuss. In the remainder of this section we provide an overview of the most commonly used behaviors supported by lists. If you are interested in digging deeper into the use of lists right now, continue reading this section. Otherwise, feel free to skip ahead to Section 2.3 on page 49, but please keep these pages in mind as a reference when the time comes to write more significant programs using lists.

Getting documentation

Although we have promised an introduction to other behaviors, and will provide this shortly, here is probably a good place to point out that it is difficult to memorize the proper use of each and every method. More important is to have a general idea of what can be accomplished and then to seek documentation when it comes time to use a feature. This is not just true of thelistclass, but of any class that you use.

Documentation can be found at http://www.python.org/ or in many other resources. More conveniently, the Python interpreter provides its own direct documenta-tion using a command calledhelp. Let’s say you want to use theinsertmethod of thelist class and you know it takes two parameters (the new item and the desired index), but can-not remember in which order they are sent. The standard documentation can be accessed as follows:

>>> help(list.insert) Help on method_descriptor: insert(...)

L.insert(index, object) -- insert object before index Given a prototypicallistinstanceL, this describes the syntaxL.insert(index, object)used when invoking the method. Based on the description, the first parameter is the desired index and the second is the object to insert. More generally, we can get documentation on the completelistclass by typinghelp(list)rather thanhelp(list.insert).

That said, it is nice to have written documentation as a reference. For this reason, Figure 2.2 contains our own brief description of the most commonly used methods of the listclass.

We organize these behaviors into three major categories: (1) those that mutate the list, (2) those that return information about the current state of the list, and (3) those that generate a new list that is in some way modeled upon the contents of one or more existing lists. For each entry, we demonstrate a prototypical syntax and provide a brief description of the semantics.

Behaviors that modify an existing list (i.e., mutators) data.append(val) Appendsvalto the end of the list.

data.insert(i, val) Insertsvalfollowing the firstielements of the list.

data.extend(otherlist) Adds the contents ofotherlistto the end of this list.

data.remove(val) Removes the earliest occurrence ofvalfound in the list.

data.pop( ) Removes and returns the last element of the list.

data.pop(i) Removes and returns the element with indexi. data[i] = val Replaces the element at indexiwith givenval. data.reverse( ) Reverses the order of the list’s elements.

data.sort( ) Sorts the list into increasing order.

Behaviors that return information about an existing list (i.e., accessors) len(data) Returns the current length of the list.

data[i] Returns the element at indexi.

val in data ReturnsTrueif the list containsval,Falseotherwise.

data.count(val) Counts the number of occurrences ofvalin the list.

data.index(val) Returns the index of the earliest occurrence ofval.

data.index(val, start) Returns the index of the earliest occurrence ofvalthat can be found starting at indexstart.

data.index(val, start, stop) Returns the index of the earliest occurrence ofvalthat can be found starting at indexstart, yet prior tostop.

dataA == dataB ReturnsTrueif contents are pairwise identical,Falseotherwise.

dataA != dataB ReturnsTrueif contents not pairwise identical,Falseotherwise.

dataA < dataB ReturnsTrueifdataAis lexicographically less thandataB,False otherwise.

Behaviors that generate a new list as a result

data[start : stop] Returns a new list that is a “slice” of the original, including elements from indexstart, up to but not including indexstop. data[start : stop : step] Returns a new list that is a “slice” of the original, including

elements from indexstart, taking steps of the indicated size, stopping before reaching or passing indexstop.

dataA + dataB Generates a third list that includes all elements ofdataA followed by all elements ofdataB.

data * k Generates a new list equivalent to k consecutive copies ofdata (i.e.,data + data + ... + data).

FIGURE 2.2: Selected list behaviors, for prototypical instances data, dataA, and dataB.

The pop method

Often we find that accessors, such ascount, return information to the caller but leave the list unchanged. Mutators, such asappend,insert, andremove, typically change the under-lying state of the list yet do not provide a return value. Thepopmethod is an interesting example that mutates the list and returns relevant information to the caller. By default,pop is used to remove the very last object from the list and to return that item to the caller. We can see the effect in the following interpreter session:

>>> groceries = ['salsa', 'pretzels', 'pizza', 'soda']

>>> groceries.pop() 'soda'

>>> groceries

['salsa', 'pretzels', 'pizza']

Notice that the immediate response upon completion of thepopcall is the display of the return value 'soda'. Behind the scene that value has also been removed from the list, so when we redisplay the list afterward we see the updated state. It is also possible to label the return value using an assignment statementnextToBuy = groceries.pop( ), as in Section 2.2.3.

There is another form ofpopthat takes a specific index as a parameter and removes and returns the element at that given index (rather than the last item of the list). For exam-ple, in the context of a restaurant waiting list, when a table opens up the person at the front of the list should be served and removed from the list, as

>>> waitlist = ['Kim', 'Donald', 'Grace', 'Andrew']

>>> toBeSeated = waitlist.pop(0)

>>> waitlist

['Donald', 'Grace', 'Andrew']

>>> toBeSeated 'Kim'

This form ofpopcan be used with any valid index (positive or negative). The two forms ofpopare not separate methods, but one and the same. This is our first example of what is called an optional parameter. The formal signature of the method ispop(i); if the caller does not explicitly send an index, the value−1is assumed, thereby popping the last ele-ment of the list.

We wish to emphasize thatpopis most appropriately used when there is a desire to remove an element from a list based upon a known index. When you have knowledge of the value and the index, it is much better to rely onpopthanremove. A call toremove requires extra time to search for the given value; worse yet, it may find one that occurs earlier than the one you intended to delete. Also,popis not the proper choice when trying to replace one element with another. The ideal syntax in that case is

>>> data[i] = newValue

Although it is possible to simulate the same effect using a combination of pop and insert, as

>>> data.pop(i)

>>> data.insert(i, newValue)

this is unnecessarily inefficient. The assignment syntaxdata[i] = newValuecaters to just this kind of replacement. The disadvantage of the latter approach is that for a very long list, a lot of time is wasted shifting the elements of the list after the old item is removed, and once again shifting elements to make room for inserting the new value.

Other mutators

Lists support three other mutators, each of which is valuable when used properly. The first of these is namedextend. It is a close relative ofappendbut with different semantics.

appendis used to add a single element to the end of an existing list. Yet sometimes we may have a sequence of elements that we wish to add to a list. We could append them one at a time, but it is easier to add then en masse using theextendmethod.

>>> groceries = ['cereal', 'milk']

>>> produce = ['apple', 'oranges', 'grapes']

>>> groceries.extend(produce)

>>> groceries

['cereal', 'milk', 'apple', 'oranges', 'grapes']

>>> produce

['apple', 'oranges', 'grapes']

Theextendmethod takes a single parameter that is another sequence of items. This causes each element contained in the parameter sequence to be added onto the end of the indicated list, while leaving the state of the parameter itself unaffected. In our example, notice that 'apples', 'oranges' and 'grapes' have been added to the end of thegroceries list (yet still remain on theproducelist).

In describingextend, we used the syntaxgroceries.extend(produce). This com-mand can easily be confused with groceries.append(produce), which is syn-tactically legal but with a very different effect. Since a list is an object, it can be added to another list as a single element. So in our earlier configu-ration, the commandgroceries.append(produce)causes groceriesto become a list with three elements, the string 'cereal', the string 'milk' and the list ['apple','oranges','grapes']. Python would display this new list as ['cereal','milk', ['apple','oranges','grapes'] ].

The other two mutators are used to rearrange the overall order of a list. Thereverse method rearranges the list to be in the opposite order and thesortmethod alphabetizes the list. This is demonstrated with our previous grocery list.

>>> groceries.reverse()

>>> groceries

['grapes', 'oranges', 'apple', 'milk', 'cereal']

>>> groceries.sort()

>>> groceries

['apple', 'cereal', 'grapes', 'milk', 'oranges']

Notice that neither of these methods accepts any parameters; there is no additional infor-mation to pass other than the clear desire for the list to be rearranged with the given method.

It is very important, however, that the empty parentheses be included. The parentheses let the interpreter know that it should call the method. Without the parentheses, the expression groceries.sortis valid syntax; it serves to identify the name of the method itself. But if the intended goal is to sort the list, this will not work.

Another common mistake is to use a command like groceries = groceries.sort( ). Unfortunately, the effect of this statement is disastrous. Calling thesort method is not a problem in and of itself. The problem is the assignment statement. This reassigns the groceriesidentifier to the value returned by thesortmethod. However, nothing is returned.

We see evidence of the problem in the following simple session:

>>> groceries = ['milk', 'bread', 'cereal']

>>> groceries = groceries.sort()

>>> groceries

>>>

A diagram of the semantics in this case is given in Figure 2.3. Although the original list was indeed sorted as part of the process, by the end of the session we no longer have a way to reference the list. Thegroceriesidentifier has been reassigned to a special Python value None, signifying a nonexistent object.

When calling a method that does not require any parameters, it is still necessary to include a set of parentheses, as ingroceries.sort( ). The expressiongroceries.sort is the name of the method, not an invocation of the method.

str

'cereal' str groceries

list

'bread' str 'milk'

'milk' str

groceries list

None 'cereal'

str 'bread'

str

FIGURE 2.3: A before-and-after view of the command groceries = groceries.sort( ).

Additional accessors

There are several other ways to access pertinent information about a list. Let’s return to the restaurant waiting list example. If someone walks into the restaurant and wants to know how many people are already on the waiting list, this can be determined using the syntax len(waitlist).

>>> waitlist = ['Kim', 'Donald', 'Grace', 'Andrew']

>>> len(waitlist) 4

Another common task is to find out whether someone is already on the waiting list.

One way to do this is to count the number of occurrences of the name and see whether it is greater than zero. But there is a more direct way. The syntax 'Michael'in waitlist, evaluates accordingly toTrueorFalse.

>>> waitlist = ['Kim', 'Donald', 'Grace', 'Andrew']

>>> 'Michael' in waitlist False

>>> 'Grace' in waitlist True

Another common question when waiting in a restaurant is “where am I on the list?”

Presuming that someone is actually on the list, we can determine how far he is from the front by using a method namedindex. In its simplest form, this method takes one parame-ter, which is a value that is assumed to be on the list. It returns an integer that denotes the index of that element within the list using the standard zero-indexed convention.

>>> waitlist = ['Kim', 'Donald', 'Grace', 'Andrew']

>>> waitlist.index('Donald') 1

In this case we find that 'Donald' has one person ahead of him. When usingindexit is imperative that you ensure the parameter exists on the list (presumably because you previ-ously checked withinorcount). An error occurs otherwise, demonstrated as follows:

>>> waitlist.index('Michael') Traceback (most recent call last):

File "<stdin>", line 1, in -toplevel-ValueError: list.index(x): x not in list

When multiple occurrences of an item are on a list, theindex method returns the earliestsuch position.

>>> waitlist = ['Rich', 'Elliot', 'Alan', 'Karl', 'Alan']

>>> waitlist.index('Alan') 2

If we want to find other positions, there exists an alternative syntax. Theindexmethod accepts an optional second parameter, which is treated as a starting index for the search.

As an example, consider the following:

>>> waitlist.index('Alan', 3) 4

This call asks the list to find the first occurrence of 'Alan' starting the search from index 3 rather than from the beginning of the list. Most often, this starting parameter is based upon a previous return value, as shown here.

>>> firstIndex = waitlist.index('Alan')

>>> secondIndex = waitlist.index('Alan', firstIndex + 1)

After locating the index of the first occurrence, we intentionally start the second search one spot furtherin the list. As was the case withpop, this alternative form is really based on an optional parameter. index('Alan')is treated similarly toindex('Alan', 0), finding the first occurrence starting from index 0. In fact, there exists an optional third parameterstop. A call toindex(val, start, stop)returns the index of the first occurrence ofvalthat can be found starting from indexstart, yet prior to indexstop(or causes an error if none is found).

Finally, we introduce operators used to compare the contents of one list to another.

For example with two popular restaurants on the same street, there may be many people who impatiently put themselves on both waiting lists. Perhaps both lists end up looking the same. We can compare them to each other using a syntaxwaitlistA == waitlistB. This returnsTrueif the elements match each other in the respective order, andFalseotherwise.

This is typically called an equivalence test. Being equivalent does not mean that the two restaurants are maintaining a single list; it means that the same values appear and in the same order. We use the==operator (pronounced “double equal”) for testing equivalence as opposed to the single equal sign=, which is used when assigning an identifier to a value.

There is also the !=(pronounced “not equal”) operator that has the opposite semantics of==. The expressionwaitlistA != waitlistBwill beTruewhen the lists do not match, and Falsewhen they do match.

Generating new lists

Some operators exist to create new lists based upon the contents of existing lists. For exam-ple, the+operator is used to “add” two lists, creating a third list that has the contents of the first followed by the contents of the second list.

>>> produce = ['apple', 'orange', 'broccoli']

>>> drygoods = ['cereal', 'bread']

>>> groceries = produce + drygoods

>>> groceries

['apple', 'orange', 'broccoli', 'cereal', 'bread']

It is worth noting that the third list is created based upon the current contents of the other two lists at that time the addition was performed. Once created, it is an independent list.

Subsequent changes to the original list do not affect the third list, as seen in the following example:

Be careful not to confuse the=and==operators. The single equal sign (=) is the assignment operator. The syntaxa = bis used to assign identifierato whatever object is referenced by identifierb. The double equal sign (==) is the equivalence operator. The syntaxa == bis used to test the equivalence of the two objects.

>>> produce.append('grapes')

>>> groceries

['apple', 'orange', 'broccoli', 'cereal', 'bread']

You can also create a list that consists of multiple consecutive copies of an existing list. The syntax is based on use of the *operator, which is traditionally used to denote multiplication. ThusmyList * 3produces the same result asmyList + myList + myList. A convenient use of this syntax is to initialize a list of identical items. For example, we might wish to track spending for each month of a year by maintaining a list of 12 values, each of which is initially zero. We can accomplish this succinctly as

>>> monthlySpending = [ 0 ] * 12

>>> monthlySpending

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

We included square brackets in the expression[ 0 ]to denote this part as a list, whereas12 is an integer. We could equivalently write12 * [0]. However, it would be quite a different thing to write0 * 12, which denotes standard multiplication of numbers and it is illegal to write[ 0 ] * [ 12 ], as a list cannot be multiplied by another list.

Finally we can create a new list that is a sublist of another by using a notation known as slicing. The syntaxgroceries[ start : stop ]produces a new list containing all elements of the original list that were stored starting at indexstartup to but not including indexstop. We will discuss this notion more fully on page 51, in the context of strings.

In document OO Programming in Python (Page 59-67)