Text: Strings in Depth ✂️
Most programs are mostly text: names, messages, files, web pages, prompts to language models. Python's text handling is one of the best there is.
f-strings: the only way you need
Putting values inside text used to be awkward. Since Python 3.6 there is one obviously
correct way, and it is lovely. Put an f before the quote and then put
expressions inside curly braces.
name = "Guybrush"
insults = 7
print(f"{name} knows {insults} insults.")
print(f"After tonight, {insults + 3}.")
print(f"His name in capitals is {name.upper()}.")
Guybrush knows 7 insults.
After tonight, 10.
His name in capitals is GUYBRUSH.
Anything that produces a value can go in the braces: maths, function calls, anything.
Formatting inside the braces
price = 1234.5678
ratio = 0.8734
name = "grog"
print(f"{price:.2f}") # two decimal places
print(f"{price:,.2f}") # thousands separators too
print(f"{ratio:.1%}") # as a percentage
print(f"{name:>12}|") # right aligned in 12 characters
print(f"{name:<12}|") # left aligned
print(f"{name:^12}|") # centred
print(f"{42:04d}") # padded with zeros
1234.57
1,234.57
87.3%
grog|
grog |
grog |
0042
Those alignment tools are how you print a table that lines up without fighting it. There is a whole format specification mini-language behind them; the seven above cover nearly everything you will ever need.
Put = after a variable inside the braces and Python prints the name as well as the value. It is the fastest debugging tool in the language and far too few people know about it.
total = 47
items = 3
print(f"{total=} {items=} {total / items=:.2f}")
total=47 items=3 total / items=15.67
Strings are sequences
A string is a row of characters, each with a numbered position. Python counts from zero, which feels wrong for about a week and then feels obvious forever.
word = "MONKEY"
# 012345
# -654321 (negative numbers count from the right)
print(word[0])
print(word[3])
print(word[-1])
print(len(word))
M
K
Y
6
Slicing: taking a piece
title = "The Secret of Monkey Island"
print(title[0:3]) # from 0, up to but NOT including 3
print(title[4:10]) # characters 4 through 9
print(title[:3]) # from the start
print(title[14:]) # to the end
print(title[-6:]) # the last six
print(title[::2]) # every second character
print(title[::-1]) # backwards
The
Secret
The
Monkey Island
Island
TeSce fMne sad
dnalsI yeknoM fo terceS ehT
The rule that trips everyone: the start is included, the end is not.
[0:3] gives you three characters. It looks arbitrary until you notice
[0:3] and [3:6] fit together perfectly with no gap and no
overlap, which is why it was chosen.
Do not picture the numbers on the characters. Picture them in the gaps between the characters, like fence posts: |T|h|e| with 0 before the T and 3 after the e. Slicing cuts at the posts. Suddenly it is obvious.
Strings cannot be changed
word = "grog"
# word[0] = "f" # this raises TypeError
better = "f" + word[1:]
print(word)
print(better)
grog
frog
Strings are immutable: every operation makes a new string rather than editing the old one. This sounds like a limitation and is actually a gift. It means a string handed to a function cannot be secretly altered under your feet, which removes a whole species of bug.
The methods that do the real work
messy = " The Governor, ELAINE Marley "
print(messy.strip())
print(messy.strip().lower())
print(messy.strip().upper())
print(messy.strip().title())
print(messy.strip().replace("Marley", "Threepwood"))
print(len(messy), len(messy.strip()))
The Governor, ELAINE Marley
the governor, elaine marley
THE GOVERNOR, ELAINE MARLEY
The Governor, Elaine Marley
The Governor, ELAINE Threepwood
33 27
| Method | Does | Example result |
|---|---|---|
.strip() | Remove whitespace at both ends | 'hi' |
.lower() / .upper() | Change case | 'hi' / 'HI' |
.title() | Capitalise Each Word | 'Hi There' |
.replace(a, b) | Swap every a for b | 'ho ho' |
.split(sep) | Break into a list | ['a', 'b'] |
.join(items) | Glue a list together | 'a-b' |
.startswith(x) | True or False | True |
.find(x) | Position, or -1 if absent | 4 |
.count(x) | How many times | 2 |
.zfill(n) | Pad with leading zeros | '007' |
split and join, the two workhorses
crew = "Guybrush,Elaine,Otis,Meathook"
members = crew.split(",")
print(members)
print(len(members), "crew members")
print(" and ".join(members))
print("\n".join(members))
['Guybrush', 'Elaine', 'Otis', 'Meathook']
4 crew members
Guybrush and Elaine and Otis and Meathook
Guybrush
Elaine
Otis
Meathook
split and join are opposites, and between them they handle a
startling proportion of real-world data work. Note the slightly backwards-looking
separator.join(list) order. Everyone writes it the wrong way round the
first ten times.
Escapes: characters with special jobs
print("Line one\nLine two")
print("Column\tone\tColumn two")
print("She said \"hello\" to me.")
print('It\'s fine with single quotes too.')
print("A backslash: \\")
print(r"A raw string ignores escapes: C:\new\table")
Line one
Line two
Column one Column two
She said "hello" to me.
It's fine with single quotes too.
A backslash: \
A raw string ignores escapes: C:\new\table
That last one matters on Windows and in Lesson 25 on regular expressions.
"C:
ew" contains a newline, because \n is special.
r"C:
ew" is the literal text you meant.
Multi-line strings
sign = """
==========================
STAN'S PREVIOUSLY OWNED
VESSELS
==========================
"""
print(sign)
==========================
STAN'S PREVIOUSLY OWNED
VESSELS
==========================
Three quotes let a string span lines. These are also how Python documents functions (Lesson 17) and how you will write prompts for a language model in Level 6.
Clean up user input
Someone typed " ELAINE.MARLEY@melee.gov ". Produce a tidy lowercase address with no surrounding spaces, then print just the part before the @ and just the domain.
Reveal solution
raw = " ELAINE.MARLEY@melee.gov "
email = raw.strip().lower()
user, domain = email.split("@")
print(f"clean: {email}")
print(f"user: {user}")
print(f"domain: {domain}")
clean: elaine.marley@melee.gov
user: elaine.marley
domain: melee.govInitials
From a full name like "guybrush ulysses threepwood", print initials in the form G.U.T.
Reveal solution
full = "guybrush ulysses threepwood"
parts = full.split()
initials = ".".join(part[0].upper() for part in parts) + "."
print(initials)
G.U.T.That for inside the brackets is a generator expression, and you meet it properly in Lesson 16. Reading it aloud works: 'the first letter, uppercased, of each part'.
Is it a palindrome?
Check whether a phrase reads the same backwards, ignoring case and spaces. Test it on "Never odd or even".
Reveal solution
phrase = "Never odd or even"
cleaned = phrase.lower().replace(" ", "")
print(cleaned)
print(cleaned == cleaned[::-1])
neveroddoreven
True[::-1] is the idiomatic Python reverse. It reads as 'the whole thing, stepping backwards', and once you have seen it twice you will never forget it.