Strings
Text is the first data structure you will index and slice, and the first one that is immutable. That word does most of the work in this unit: string methods never change the string they are called on, they return a new one, and forgetting that produces code that looks right and does nothing.
- Index and slice with confidence, including negative indices.
- Explain why
s.upper()alone accomplishes nothing. - Build strings in a loop and know why
joinis better. - Pick the right method from the common set.
Almost all data arrives as text: files, web responses, user input. Getting from text to structured values is the first half of nearly every real program, and slicing is the basic tool.
Parsing dates, cleaning names, splitting CSV lines. The Heartbleed bug of 2014 was a length field trusted without checking, letting a read run past the end of a buffer — an indexing error with global consequences.
Python 3 strings are sequences of Unicode code points, not bytes, which
is why len('café') is 4 rather than 5. That change from
Python 2 broke a great deal of code and is the main reason the migration took
a decade.
Indexing and slicing
s[i] is the character at position i, counting
from 0. Negative indices count from the end, so
s[-1] is the last character. s[a:b] is the slice
from a up to but not including b.
s = 'computer' # 01234567 # -87654321 s[0] # 'c' s[-1] # 'r' s[1:4] # 'omp' - positions 1, 2, 3 s[:3] # 'com' - start defaults to 0 s[5:] # 'ter' - end defaults to len(s) s[:] # 'computer' - a full copy s[::2] # 'cmue' - every second character s[::-1] # 'retupmoc' - reversed
The excluded endpoint is the same convention as range, and
it has the same payoff: s[:i] + s[i:] reconstructs
s for any i, with no off-by-one
adjustment.
s[100] raises IndexError. But
s[100:200] quietly returns ''. Slicing clamps to
the valid range, which is convenient and occasionally hides a bug — an
empty result may mean your indices were nonsense rather than that the data
was empty.
Immutability
No operation modifies a string in place. s[0] = 'x' is a
TypeError. Every method that appears to alter a string in fact
returns a new one, and the original is untouched.
name = 'ada' name.upper() # computes 'ADA' and throws it away print(name) # 'ada' - unchanged name = name.upper() # the return value has to be captured print(name) # 'ADA'
If a line consists only of something.method() on a string
and nothing is assigned, that line does nothing at all.
# works, but creates a new string every round
out = ''
for w in words:
out = out + w + ' '
# idiomatic and faster
out = ' '.join(words)out + w copies
everything accumulated so far. Over $n$ words that is $n^2$ character copies
— a Unit 9 concern that shows up here first.join is called on the separator,
which reads backwards at first but means any separator works:
', '.join(names).The methods worth memorising
s = ' Hello World '
s.strip() # 'Hello World' - removes leading/trailing whitespace
s.lower() # ' hello world '
s.replace('l', 'L') # replaces every occurrence
s.split() # ['Hello', 'World'] - splits on any whitespace run
'a,b,c'.split(',') # ['a', 'b', 'c'] - or on a given separator
s.find('World') # 8 - index, or -1 if absent
s.count('l') # 3
'World' in s # True - the readable membership test
'abc'.startswith('a')# True
'42'.isdigit() # True - useful before int()in when you only need to know whether
something is present. It is the clearest.find when you need the position and can
handle -1 for absent.index when absence is a genuine error;
it raises ValueError rather than returning a sentinel that can
be used by accident as a valid index.This is the trap in find. If you write
s[s.find('z')] and 'z' is absent, the
-1 silently gives you the last character rather than an
error. Always test the result of find before using
it.
• Calling a method and discarding the result. Strings are immutable;
capture the return value.
• Assuming split() and split(' ') behave the
same. The first collapses runs of whitespace, the second does not, so
'a b'.split(' ') gives a stray empty string.
• Forgetting the excluded endpoint on slices.
• Using find without checking for -1.
Practice
Predict each answer before running it. Original exercises written for this guide in the style of the course's weekly sets.
For s = 'abcdef', what are s[2:5],
s[-2:], s[::-1]?
Answer
'cde',
'ef', 'fedcba'.Why does this print hello?
s = 'hello'
s.replace('h', 'j')
print(s)Answer
replace
returns a new string; nothing captured it. Write
s = s.replace('h', 'j').Write a function testing whether a string reads the same backwards.
Solution
def is_palindrome(s: str) -> bool:
"""Return whether s reads the same forwards and backwards,
ignoring case and spaces.
>>> is_palindrome('Race car')
True
>>> is_palindrome('hello')
False
"""
clean = s.lower().replace(' ', '')
return clean == clean[::-1]lower() then replace() works because
each returns a new string for the next to act on.What do 'a b'.split() and 'a b'.split(' ')
give?
Answer
['a', 'b']
and ['a', '', 'b']. With no argument, runs of whitespace count
as one separator; with an explicit ' ', each space separates,
so the double space yields an empty field.Turn 'ada lovelace' into 'A.L.'.
Solution
def initials(name: str) -> str:
"""Return the dotted uppercase initials of name.
>>> initials('ada lovelace')
'A.L.'
"""
return ''.join(w[0].upper() + '.' for w in name.split())What does 'hello'[('hello'.find('z'))] give, and why is
that bad?
Answer
'o'.
find returned -1, which is a legal index meaning
"last". You get a plausible-looking wrong answer instead of an
error.Count the words in a sentence.
Solution
len(sentence.split()).
Splitting on whitespace and taking the length handles multiple spaces and
leading or trailing space correctly, which counting spaces would
not.Remove all vowels from a string.
Solution
def strip_vowels(s: str) -> str:
"""Return s with all vowels removed.
>>> strip_vowels('education')
'dctn'
"""
return ''.join(ch for ch in s if ch.lower() not in 'aeiou')Expanded topic checklist
Course coverage. This unit is part of the complete CSC108H1 topic map. It should cover the following items before you move on:
- Indexing and slicing. Positive and negative indices, defaults, step, and the excluded endpoint.
- Immutability. Why methods return rather than modify.
- Common methods. strip, split, join, replace, find, count, membership.
- Building strings. Why
joinbeats repeated concatenation.
The calendar lists strings among the elementary data types. Check the syllabus on Quercus for the assigned exercises and for which methods the course expects you to know without lookup.
Strings are immutable sequences. Unit 5 introduces lists, which are mutable sequences with almost the same indexing — and the difference between the two is where the aliasing trap lives.
Deep practice extensions
Use these after the main practice set. They push on definitions and edge cases rather than routine calculation.
What happens on s[0] = 'x'?
Guide
TypeError:
strings do not support item assignment. Build a new string
instead.What does s[10:20] give on a short string?
Guide
''. Slicing
clamps rather than raising, unlike indexing.Why is join a method of the separator?
Guide
Why is repeated += on a string slow?