Python · Beginner
Strings in Python
In short: A Python string (str) is an immutable sequence of characters written in single or double quotes. You read characters by index, take parts with slices, join with +, and transform text with methods such as upper(), strip(), split() and replace(), each of which returns a new string because the original can never change.
Text as a sequence
Text is the most common kind of data a program handles: names, addresses, lines of a file, everything input() returns. Python stores text in the str type. A string literal is written between matching quotes, single or double; the choice makes no difference to the value, which lets you write "it's" without escaping anything. Triple quotes allow a literal to span several lines. Inside a string, \n stands for a line break and \t for a tab.
A string behaves as a sequence. len(s) counts its characters, s[0] is the first character and s[-1] the last. A slice s[start:stop] takes characters from start up to but not including stop; leaving out start means from the beginning, leaving out stop means to the end, and s[::-1] reverses. Slicing never fails on out-of-range bounds, it just clips, whereas indexing a single position past the end raises IndexError. A for loop over a string visits one character per round, and in tests whether one string appears inside another.
Strings are immutable. s[0] = "X" is a TypeError. Every operation that seems to modify a string, from upper() to replace(), actually builds and returns a new one, so the result must be assigned somewhere: name = name.strip(). Immutability is what makes strings safe to share between parts of a program and to use as dictionary keys.
The str type has dozens of methods; a handful cover most work. strip() removes surrounding whitespace, which matters for input. lower() and upper() change case, usually so that a comparison ignores it. split() cuts a string into a list of words, or on a separator you pass, and ", ".join(items) does the reverse. replace(old, new) swaps every occurrence. startswith(), endswith(), find() and count() ask questions about content.
For building text, f-strings (Python 3.6 and later) are the tool: an f before the opening quote lets you put expressions in braces, as in f"{qty} x {name}", with an optional format spec after a colon. {price:.2f} prints two decimals and {n:>5} right-aligns in five columns. They replace error-prone chains of + and str().
Syntax
s = "text" or 'text' # the same value
s[i] s[-1] # index from the front / from the end
s[a:b] s[a:] s[:b] # slice: a up to (not including) b
len(s) "sub" in s
s.strip() s.lower() s.upper()
s.split() s.split(",") ", ".join(parts)
s.replace("old", "new") s.startswith("x") s.count("e")
f"{value} and {price:.2f}" # f-string with a format specEvery method returns a new string; the original is unchanged. join() is called on the separator and given the list of parts.
Common string methods
| Method | What it returns | Example |
|---|---|---|
| strip() | a copy without leading and trailing whitespace | " hi ".strip() gives "hi" |
| lower() / upper() | a copy in lower or upper case | "Ab".lower() gives "ab" |
| split(sep) | a list of parts; with no argument, splits on whitespace | "a,b".split(",") gives ["a", "b"] |
| join(parts) | one string with the separator between the parts | "-".join(["a", "b"]) gives "a-b" |
| replace(old, new) | a copy with every old replaced by new | "aXa".replace("X", "-") gives "a-a" |
| find(sub) | index of the first match, or -1 | "hello".find("l") gives 2 |
| count(sub) | number of non-overlapping matches | "banana".count("a") gives 3 |
| startswith(x) / endswith(x) | True or False | "file.py".endswith(".py") gives True |
Indexing and slicing a booking code
One twelve-character code, taken apart by position.
code = "LHR-DEL-0725"
print(len(code))
print(code[0], code[-1])
print(code[0:3])
print(code[4:7])
print(code[-4:])
print(code[::-1])
print("DEL" in code, "SFO" in code)
for ch in code[:3]:
print(ch, end=".")
print()Output
12 L 5 LHR DEL 0725 5270-LED-RHL True False L.H.R.
Positions count from 0, so code[0:3] is the first three characters and code[4:7] skips the hyphen at index 3. Negative indexes count from the end, so code[-4:] is the last four characters whatever the length. The step of -1 in code[::-1] walks backwards and reverses the string. The loop shows that a slice is an ordinary string you can iterate over.
Cleaning and inspecting input
The input line is Green Tea, large with two spaces on each side.
order = input()
print("[" + order + "]")
order = order.strip()
print("[" + order + "]")
print(order.upper())
print(order.lower().replace(" ", "_"))
parts = order.split(", ")
print(parts)
print(order.startswith("Green"), order.count("e"), order.find("Tea"))Input given to the program: Green Tea, large
Output
[ Green Tea, large ] [Green Tea, large] GREEN TEA, LARGE green_tea,_large ['Green Tea', 'large'] True 4 6
The brackets make the stray spaces visible; strip() removes them and the result is assigned back, because the original string is not changed. Methods can be chained, as lower().replace() shows, since each returns a new string to call the next method on. split(", ") cuts on the exact separator and gives a list, which the lists lesson picks up. Note that order is unchanged by upper(): it still starts with a capital G on the last line, where find reports that "Tea" begins at index 6.
Building text with f-strings and join
Formatting a receipt line, a two-column header and a comma-separated list.
item = "notebook"
price = 3.5
quantity = 4
print(f"{quantity} x {item} at {price:.2f} each")
print(f"Total: {quantity * price:.2f}")
print(f"{'Item':<10}|{'Qty':>4}")
print(f"{item:<10}|{quantity:>4}")
colours = ["red", "amber", "green"]
print(", ".join(colours))
print("-" * 12)
print("ab" + "cd", "ha" * 3)Output
4 x notebook at 3.50 each Total: 14.00 Item | Qty notebook | 4 red, amber, green ------------ abcd hahaha
Anything inside the braces is evaluated, including quantity * price. The format spec after the colon controls the appearance: .2f fixes two decimals, <10 pads to ten characters left-aligned, >4 right-aligns in four. join is called on the separator and given the list. + concatenates and * repeats, which is a quick way to draw a rule line.
Common mistakes
Calling a method and ignoring the result
Why it goes wrong:
name.strip()on its own line computes a new string and throws it away;namestill has its spaces, because strings cannot change in place.Fix: Assign the result:
name = name.strip().Python · fixname = " Ola " name = name.strip() print(len(name)) # 3Trying to change one character by index
Why it goes wrong:
s[0] = "J"raises TypeError: 'str' object does not support item assignment.Fix: Build a new string from slices:
s = "J" + s[1:].Joining text and numbers with +
Why it goes wrong:
"Age: " + 30is a TypeError;+on a string only accepts another string.Fix: Use an f-string,
f"Age: {age}", or pass separate arguments to print().Forgetting that the slice end is excluded
Why it goes wrong:
s[0:3]gives three characters, at indexes 0, 1 and 2, not four. Reading it as "from 0 to 3 inclusive" is a frequent source of missing last characters.Fix: Remember that
s[a:b]has lengthb - a, and thats[:3] + s[3:]is always the whole string.
Where you use this
Comparing user input reliably needs normalising first: answer.strip().lower() in ("y", "yes") accepts "Yes", " YES " and "y" alike. Parsing a line such as Nairobi,14.5,cloudy is split(",") followed by converting the middle piece to float. Producing a report is the reverse: f-strings with width and precision specs line up columns so the output is readable, and join builds a comma-separated line from a list of values. Every exercise that reads text and prints text passes through these few methods.
line = "Nairobi,14.5,cloudy"
city, temp, sky = line.split(",")
print(f"{city:<10}{float(temp):>6.1f} {sky}")Key points
- Strings are sequences: index with
[i], slice with[a:b], loop withfor, test within. - Strings are immutable; every method returns a new string, so assign the result.
strip(),lower(),split()andreplace()cover most input clean-up.", ".join(list)turns a list into one string;split()goes the other way.- f-strings embed expressions and format specs:
f"{price:.2f}". - Slices clip at the ends; single-index access past the end raises IndexError.
Try it yourself
Read a full name and print its initials in upper case, each followed by a full stop, so that nora kaspar vale becomes N.K.V.. Use split() and indexing.
name = input()
# build the initials and print them
nora kaspar valeN.K.V.name = input()
initials = ""
for part in name.split():
initials += part[0].upper() + "."
print(initials)
Practise this
Related lessons
- Data Types in PythonThe core Python data types int, float, str, bool and None: how to check a type with type() and convert between types with int(), float() and str().8 min
- Lists in PythonHow Python lists store ordered, changeable collections: creating them, indexing and slicing, adding and removing items, sorting, copying and looping over them.9 min
- Loops in Python: for and whileHow a for loop walks through a sequence and a while loop repeats until a condition fails, with range(), enumerate(), break and continue explained by example.9 min
Frequently asked questions
Are Python strings mutable?
No. A str object never changes after it is created. Operations such as upper(), replace() and slicing return new strings, and s += "x" rebinds s to a freshly built string. When you need to assemble a long string piece by piece, collect the parts in a list and call "".join(parts) once at the end, which avoids copying the growing string on every step.
What is the difference between single and double quotes in Python?
None in meaning: 'hi' and "hi" are the same string. Pick whichever lets you avoid escaping: double quotes for text containing an apostrophe, single quotes for text containing double quotes. Triple quotes of either kind allow a literal to span multiple lines.
How do I check whether a string contains another string?
Use the in operator: "tea" in order is True or False. If you need the position, order.find("tea") returns the index of the first match or -1 when absent; order.index("tea") does the same but raises ValueError instead of returning -1. For a case-insensitive check, lower-case both sides first.
How this page was checked. Every program on it was run with CPython 3.11 at build time; runs in your browser on CPython 3.14 (Pyodide) by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.