You will handle strings more than any other type. Python gives them about forty methods; this post covers the eight you will actually use, plus the two facts that explain the rest.
Strings never change
Every string method returns a new string. None of them modify the original, because strings are immutable.
name = " Margherita "
name.strip()
print(repr(name)) # Output: ' Margherita '
name = name.strip()
print(repr(name)) # Output: 'Margherita'
The first call did the work and threw it away. You must assign the result. This single mistake accounts for most "the method did nothing" confusion, and it applies to every method below.
repr() is worth knowing here: it shows the quotes and any escape characters, so you can
see whitespace that print() hides.
Cleaning input
strip() removes whitespace from both ends — the first thing you do to anything a person
typed.
raw = "\n alice@bank.test \n"
print(raw.strip()) # Output: alice@bank.test
print(" left".lstrip() + "|") # Output: left|
print("file.txt".removesuffix(".txt")) # Output: file
removeprefix() and removesuffix() arrived in 3.9 and are the right tool
for chopping a known start or end. Do not use strip(".txt") for that — it removes any of
those characters from either end, so "text.txt".strip(".txt") gives you
"ex". That is a genuinely surprising bug.
Case
email = "Alice@Bank.TEST"
print(email.lower()) # Output: alice@bank.test
print("pizza".upper()) # Output: PIZZA
print("hello world".title()) # Output: Hello World
Normalising with lower() before comparing or storing an email address is standard
practice — users do not type consistently and you should not care.
Searching
line = "Deposit: $100.50"
print("Deposit" in line) # Output: True
print(line.startswith("Deposit")) # Output: True
print(line.endswith(".50")) # Output: True
print(line.find("$")) # Output: 9
print(line.count("0")) # Output: 3
Use in for "is it in there" — it reads better than find() != -1 and is
what everyone writes. Reach for find() only when you need the position.
index() does the same as find() but raises ValueError when
the substring is absent rather than returning -1. The silent -1 is the more
dangerous of the two, because -1 is a valid index.
Splitting and joining
These two are the workhorses. Almost every text-processing job is a split, some work, and a join.
row = "1,alice@bank.test,Alice Cooper"
fields = row.split(",")
print(fields) # Output: ['1', 'alice@bank.test', 'Alice Cooper']
print(" | ".join(fields)) # Output: 1 | alice@bank.test | Alice Cooper
print("a b c".split()) # Output: ['a', 'b', 'c']
split() with no argument splits on any run of whitespace and discards empties, which
is what you want for words. split(",") with an argument keeps empty fields, which is what
you want for data.
join() reads backwards the first time you see it: the separator is the string you call
it on. It only accepts strings, so convert numbers first —
", ".join(str(n) for n in numbers).
For real CSV, use the csv module rather than split(","). A comma inside a
quoted field will break your split, and someone's surname will eventually contain one. See
Files.
Slicing
Slicing is not a method — it is the indexing syntax, and it works on any sequence.
card = "4111111111111234"
print(card[:4]) # Output: 4111
print(card[-4:]) # Output: 1234
print(card[4:8]) # Output: 1111
print(card[::-1][:4]) # Output: 4321
The rule is [start:stop], where start is included and stop is
not. Negative numbers count from the end, so [-4:] is "the last four" — the idiom for
masking a card number. A slice never raises for being out of range; it just gives you what is there.
Replacing, and testing what a string contains
replace() swaps every occurrence, and returns a new string like everything else:
amount = "$1,200.50"
cleaned = amount.replace("$", "").replace(",", "")
print(cleaned) # Output: 1200.50
print(float(cleaned)) # Output: 1200.5
Chaining two replace() calls to strip currency formatting before converting is a
common and perfectly readable idiom. Pass a third argument to limit how many it replaces.
The is...() family answers "what kind of characters are in here", which is how you
validate input without a try/except:
print("12345".isdigit()) # Output: True
print("12.5".isdigit()) # Output: False
print("alice".isalpha()) # Output: True
print(" ".isspace()) # Output: True
print("".isdigit()) # Output: False
Two things to notice. "12.5".isdigit() is False — a decimal point is not
a digit, so this tests for whole numbers only. And the empty string is False for all of
them, which is usually the behaviour you want and occasionally the one that surprises you.
For anything more complicated than these, converting inside a try/except is clearer than assembling a test.
Do not build strings with += in a loop
Because strings are immutable, += in a loop builds a brand new string every iteration
and throws the previous one away. On a few items nobody notices. On a hundred thousand it is
quadratic.
words = ["Total", "balance", "is", "$9,650.50"]
# Slow, and not how it is written
slow = ""
for word in words:
slow += word + " "
# What to write instead
fast = " ".join(words)
print(fast) # Output: Total balance is $9,650.50
join() knows how long the result will be and allocates once. Build a list, join it at
the end — the same advice as StringBuilder in Java, with better syntax.
The ones worth memorising
| Method | Does |
|---|---|
strip() | Trim whitespace from both ends |
lower() / upper() | Change case, usually to compare |
split(sep) | String to list |
sep.join(items) | List to string |
replace(old, new) | Swap every occurrence |
startswith() / endswith() | Test an end, cheaply |
in | Test containment |
Everything else you can look up when you need it. Next: f-strings, which is how you assemble strings rather than take them apart.