Chapter 8 · pp. 93–104
Strings
A string is more than a single value like an integer or a float. It is an ordered collection of characters, so you can look inside it, cut pieces out of it, and search it. This chapter covers how to reach individual characters, how to loop over them, how to take slices, and how to use string methods and comparisons.
Strings as sequences and indexing
A string is a sequence: an ordered collection of smaller items. Here each item is one character. You reach a character with the bracket operator. The number inside the brackets is called the index, and it tells Python which character you want (p. 93).
With fruit = 'banana', the expression fruit[1] picks out one character and gives 'a', not 'b'. The reason is that an index is an offset from the start of the string. The first character is zero steps from the start, so it has index 0. That makes fruit[0] equal to 'b', fruit[1] equal to 'a', and fruit[2] equal to 'n' (p. 93).
The index can be any expression that produces an integer. If i = 1, then fruit[i] is 'a' and fruit[i+1] is 'n'. A float index such as fruit[1.5] is not allowed and raises a TypeError (pp. 93–94).
Length and negative indices
The built-in function len returns the number of characters in a string. len('banana') is 6 (p. 94).
Because counting starts at 0, the valid indices run from 0 to len(s) - 1. For 'banana' that means 0 to 5. Asking for fruit[6], or fruit[len(fruit)], raises an IndexError because that position does not exist (p. 94).
To get the last character you can write fruit[len(fruit)-1]. A shorter way is a negative index, which counts backward from the end. fruit[-1] is the last letter, fruit[-2] is the second to last, and so on (p. 94).
Traversal
Traversal means processing a string one character at a time, from the first character to the last. You do something to each character along the way (p. 94).
One way is a while loop with an index:
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1
The loop stops when index equals len(fruit), because the condition is then false. The last character used has index len(fruit)-1 (p. 94).
A for loop is shorter: for letter in fruit: gives the variable letter each character in turn, until none are left (pp. 94–95).
Traversal can also build new text. Joining a prefix letter to a suffix with + (concatenation) inside a loop makes a series:
prefixes = 'JKLMNOPQ'
suffix = 'ack'
for letter in prefixes:
print(letter + suffix)
This prints Jack, Kack, Lack, and so on in alphabetical order (p. 95).
Slices and the empty string
A slice is a segment of a string. The form s[n:m] gives the characters from index n up to, but not including, index m. With s = 'Monty Python', s[0:5] is 'Monty' and s[6:12] is 'Python' (p. 95).
The excluded end can seem odd. It helps to picture the indices as pointing to the gaps between characters, not at the characters. A slice then runs from one gap to another (p. 95).
You can leave out either index:
- Leaving out the first index starts the slice at the beginning.
'banana'[:3] is 'ban'.
- Leaving out the second index runs the slice to the end.
'banana'[3:] is 'ana'.
(pp. 95–96)
When the first index is greater than or equal to the second, the result is an empty string. For example, fruit[3:3] gives ''. An empty string has no characters and length 0, and it is written as two quotation marks with nothing between them. Apart from its length, it behaves like any other string (p. 96).
Strings are immutable
A common mistake is to try to change one character by assigning to it:
greeting = 'Hello, world!'
greeting[0] = 'J'
This does not work. It raises a TypeError, because strings are immutable: an existing string cannot be changed. You can still get a modified version by building a new string. 'J' + greeting[1:] gives 'Jello, world!'. The new string is made from a new first letter and a slice of the old one, and the original greeting is untouched (p. 96).
Two traversal patterns: search and counter
A search is a traversal that stops as soon as it finds what it wants. The function find(word, letter) checks each index. If word[index] == letter, it returns that index at once. A return inside a loop ends the whole function immediately. If the loop finishes with no match, the function returns -1 (pp. 96–97). In 'banana', searching for 'a' returns 1, and searching for 'z' returns -1. In a sense find does the reverse of the bracket operator: you give it a character and it gives back an index (p. 97).
The counter pattern counts how often something happens. Set a variable to 0 before the loop. Inside the loop, add 1 each time the item you are counting appears:
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print(count)
After the loop, count holds 3, the number of a's (p. 97).
String methods and the in operator
A method is like a function: it takes arguments and returns a value. The difference is syntax. A method uses dot notation, so word.upper() instead of upper(word). Calling a method is called an invocation. With word = 'banana', word.upper() returns a new string, 'BANANA' (pp. 97–98).
Strings also have a find method, and it is more general than the function above. It can look for substrings as well as single characters. word.find('na') returns 2. It also accepts optional arguments:
- A second argument sets where to start.
word.find('na', 3) returns 4.
- A third argument sets where to stop. The stop index is excluded, just like in slices.
'bob'.find('b', 1, 2) returns -1 because the range covers only index 1.
(p. 98)
The in operator takes two strings. It returns True if the first appears as a substring of the second, and False otherwise. 'a' in 'banana' is True, and 'seed' in 'banana' is False (p. 98). You can use it inside a loop. This function prints each letter of word1 that is also in word2:
def in_both(word1, word2):
for letter in word1:
if letter in word2:
print(letter)
Called with 'apples' and 'oranges', it prints a, e, and s (pp. 98–99).
Comparing strings
The relational operators ==, <, and > work on strings. Equality tests whether two strings match. The other operators order strings (p. 99).
However, Python does not order words the way a person would. Every uppercase letter comes before every lowercase letter. So 'Pineapple' < 'banana' is True, even though a person would put banana first. The usual fix is to convert both strings to the same case, such as all lowercase, before comparing them (p. 99).
Check yourself
- For
fruit = 'banana', what do fruit[0], fruit[-1], and fruit[len(fruit)] give? Explain each result.
- What do
fruit[:3], fruit[3:], and fruit[3:3] return, and why does the last one behave as it does?
- Why does
greeting[0] = 'J' fail, and how can you get a string that starts with 'J' instead?
- Write a loop that counts how many times the letter 'n' appears in 'banana'. What must you do to the counter variable before the loop starts?
- Why is
'Zebra' < 'apple' true in Python, and how could you change the comparison so it matches alphabetical order?