String Manipulation
Indexing, slicing, the string methods you will actually use, and why text.upper() appears to do nothing at all.
Indexing and slicing
A string is a sequence of characters, and every character has a position, counting from zero.
word = "Computing"
# 012345678
print(word[0]) # C
print(word[3]) # p
print(word[-1]) # g, the last character
print(word[-2]) # n, the second from last
print(len(word)) # 9len(word) - 1word[9] on a nine character string raises an IndexError, because the positions run 0 to 8. Negative indexing with word[-1] is the safe way to reach the end.
Slicing
A slice takes a section of the string, using [start:end]. The start is included and the end is excluded.
word = "Computing"
print(word[0:4]) # Comp
print(word[4:]) # uting, from 4 to the end
print(word[:4]) # Comp, from the start to 4
print(word[:]) # Computing, the whole thing
print(word[::-1]) # gnitupmoC, reversed
print(word[0:9:2]) # Cmuig, every second characterIt means word[:4] and word[4:] fit together perfectly with no overlap and nothing missing. The length of word[a:b] is always b - a, which is far easier to reason about than it first looks.
Looping through a string
for letter in "Python":
print(letter)You rarely need the index. If you do want both the position and the character, use enumerate:
for i, letter in enumerate("Python"):
print(i, letter)- Every character has an index. The first is 0, and negative indexes count back from the end.
- A slice [a:b] runs from index a up to, but not including, b. Leave b out to go to the end.
- [-1] is the last character.
- Functions and methods build and search strings, and each one gives back a new string.
String methods
text = " Hello World "
print(text.strip()) # "Hello World", removes surrounding spaces
print(text.upper()) # " HELLO WORLD "
print(text.lower()) # " hello world "
print(text.title()) # capitalises each word
print(text.replace("l", "L")) # swaps every match
print(text.strip().split(" ")) # ["Hello", "World"]
print("-".join(["a", "b", "c"])) # "a-b-c"
print(text.count("l")) # 3
print(text.find("World")) # 8, the position, or -1 if not found
print("Hello" in text) # Truetext.upper() does not change text. It returns a brand new string and leaves the original exactly as it was. You must write text = text.upper() to keep the result. This single misunderstanding causes more confusion than any other string bug.
name = "aisha"
name.title() # returns "Aisha", but throws it away
print(name) # aisha, unchanged
name = name.title() # this is what you meant
print(name) # Aishasplit and join are opposites
line = "Aisha,Ben,Chloe"
names = line.split(",") # ["Aisha", "Ben", "Chloe"]
back = ",".join(names) # "Aisha,Ben,Chloe"split turns one string into a list. join turns a list back into one string. Note that join is called on the separator, not on the list, which catches everybody out once.
Case insensitive comparison
reply = input("Continue? ")
if reply.lower() == "yes": # accepts Yes, YES, yes, yEs
print("Carrying on")Converting both sides to the same case before comparing is the standard way to stop a capital letter breaking a comparison.
- upper() makes every letter a capital.
- strip() removes spaces from both ends.
- replace() swaps every match for something else.
- split() breaks a string into a list.
- join() builds a string from a list.
- find() gives the position of the first match, or -1 if there is none.
- A method returns a new string. The original is never changed unless you assign the result.
Validating and building text
Checking what a string contains
"42".isdigit() # True
"abc".isalpha() # True
"abc123".isalnum() # True
" ".isspace() # True
"".isdigit() # False, an empty string is never validisdigit() is the standard way to check that input can safely be converted with int():
reply = input("Enter a number: ")
if reply.isdigit():
number = int(reply)
print(f"Twice that is {number * 2}")
else:
print("That was not a whole number.")f-strings
The clearest way to build text that contains values.
name = "Aisha"
score = 92
print(f"{name} scored {score} out of 100")
print(f"Rounded: {3.14159:.2f}") # 3.14
print(f"As a percentage: {score / 100:.1%}") # 92.0%
print(f"Padded: {name:>10}") # right aligned in 10 charactersBuilding output with + and str() is longer, easier to get wrong, and crashes the moment you forget a conversion. An f-string handles the conversion for you and reads in the order the sentence is spoken.
Two classic string problems
Counting vowels:
word = input("Enter a word: ").lower()
vowels = 0
for letter in word:
if letter in "aeiou":
vowels += 1
print(f"{word} contains {vowels} vowels")Checking a palindrome:
phrase = input("Enter a phrase: ").lower().replace(" ", "")
if phrase == phrase[::-1]:
print("That is a palindrome")
else:
print("That is not a palindrome")Both rely on normalising the text first, lowering the case and removing anything that should not count, so the comparison is fair.
Key terms you must be able to define
- String
- A sequence of characters, written inside quotation marks.
- Index
- The position of a character in a string, counting from 0.
- Slice
- A section of a string, taken with a start and an end index.
- Immutable
- Cannot be changed after creation. Strings in Python are immutable.
- Concatenation
- Joining two strings end to end with the + operator.
- split
- A string method that breaks text into a list at a chosen separator.
- join
- A string method, called on the separator, that combines a list of strings into one.
- f-string
- A string beginning with f where values written in braces are inserted directly.
- isdigit
- A string method returning True only when every character is a digit.
Flashcards
Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.
Reach the top grade on this topic
- Use negative indexes and slices confidently, including the reversing slice
[::-1] - Remember that every string method returns a new string rather than changing the original
- Use split and join to move between a line of text and a list of values
- Validate text with isdigit before converting it, rather than letting int() crash
- Use f-strings, with formatting such as
:.2f, for all output that mixes text and values
Mistakes that cost marks here
- Writing
text.upper()and expecting text to change. The result must be assigned. - Using an index equal to the length of the string, which is out of range.
- Forgetting that a slice excludes its end index.
- Calling join on the list instead of on the separator.
- Comparing input to "yes" without lowering the case first, so "Yes" fails.
- Building output with lots of + and str() instead of an f-string.
Strings in Python cannot be changed once created. Every method that looks like it edits a string actually builds a brand new one and hands it back, which is exactly why you must assign the result to something.
Knowledge check
"Computing"[0:4] give?text.upper() do to the variable text?word[::-1] give?"a,b,c".split(",") produce?f"Score: {points}" do?"Computing"[-1] give?"Hello".find("z") return?reply.lower() == "yes" rather than reply == "yes"?Exam-style questions
Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.
- LA string cannot be changed after it is created
- LMethods return a new string rather than altering the original
Immutable means that once a string has been created its contents cannot be altered. Methods such as upper, replace and strip do not change the original string at all: they build and return a brand new string, which is why the result must be assigned to a variable if it is to be kept.
- LReads the word and converts it to lower case
- LInitialises a counter to zero
- LLoops through each character in the word
- LIncreases the counter when the character is a vowel and prints the total
word = input("Enter a word: ").lower() vowels = 0 for letter in word: if letter in "aeiou": vowels += 1 print(f"{word} contains {vowels} vowels") Converting the input to lower case means capital letters are counted too. The loop walks through the string one character at a time, and the in operator checks whether the current character appears in the string of vowels, which is much shorter than writing five separate comparisons.
name.title() on its own appears to have no effect, and state the correction.3 marks- LStrings are immutable so the original cannot be changed
- LThe method builds and returns a new string
- LThe returned value must be assigned, as in name = name.title()
Strings in Python are immutable, so no method is able to alter the string it is called on. The title method builds a brand new string with each word capitalised and returns it, leaving the original completely untouched. Written on a line by itself the returned string is simply discarded, so the variable appears unchanged. The correction is to assign the result back, writing name = name.title(), which makes the name refer to the new string instead.
- LReads the line of input
- LSplits it on commas into a list
- LLoops through the resulting list
- LRemoves surrounding spaces from each name
- LPrints each name with a number
line = input("Enter names separated by commas: ") names = line.split(",") for i, name in enumerate(names, start=1): print(f"{i}. {name.strip()}") The split method breaks the line into a list at every comma. Because the user is likely to type a space after each comma, strip is used to remove any surrounding whitespace from each name before it is printed. Using enumerate with start set to 1 gives a counter beginning at 1 rather than 0, which is more natural for a numbered list shown to a user.
- LConvert the phrase to a single case
- LRemove the spaces
- LReverse the phrase using a slice with a step of -1
- LCompare the normalised phrase with its reverse
The phrase must first be normalised so that differences which should not count are removed. Converting it entirely to lower case means a capital letter at the start does not cause a mismatch, and replacing every space with nothing means the spacing of the original phrase is ignored. The normalised phrase is then reversed using the slice [::-1], which walks through the characters with a step of minus one. If the normalised phrase and its reverse are equal then the phrase reads the same in both directions and is a palindrome. Without normalising first, a genuine palindrome such as Never odd or even would be reported as not being one.