Level 1 · First Words

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.

🐛 The debugging f-string

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.

VISUAL CALCULUS[Medium: Success]

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
MethodDoesExample 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 FalseTrue
.find(x)Position, or -1 if absent4
.count(x)How many times2
.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.

Exercise 1

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.gov
Exercise 2

Initials

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'.

Exercise 3

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.

+100 XP