Python Strings
String methods, f-strings, formatting, slicing, and regex basics — everything you need to work with text in Python.
String Literals
Python supports several string literal forms, each solving a different problem. Choosing the right literal type prevents a whole class of escaping bugs before they start.
single = 'Hello'
double = "World"
triple = """Multi
line""" # spans multiple lines without \n
raw = r"C:\Users\alice" # backslashes are literal — no escape processing
bytes_ = b"binary data" # bytes literal, not a str
f_string = f"Hello, {name}" # evaluated at runtime — most common for formatting
Indexing and Slicing
Strings are sequences — zero-indexed, supporting the same slice syntax as lists and tuples. Negative indices count from the end, and a step of -1 reverses the string.
s = "Python"
s[0] # "P" — first character
s[-1] # "n" — last character (equivalent to s[5])
s[1:4] # "yth" — characters at index 1, 2, 3
s[:3] # "Pyt" — everything up to but not including index 3
s[3:] # "hon" — everything from index 3 to the end
s[::2] # "Pto" — every second character (step of 2)
s[::-1] # "nohtyP" — reversed (step of -1)
Common String Methods
String methods are one of Python’s strongest areas for text processing. Every method returns a new string — strings are immutable, so nothing here modifies text in place.
text = " Hello, World! "
# Whitespace handling
text.strip() # "Hello, World!" — removes both ends
text.lstrip() # "Hello, World! " — removes left end only
text.rstrip() # " Hello, World!" — removes right end only
# Case conversion
text.lower() # " hello, world! "
text.upper() # " HELLO, WORLD! "
text.title() # " Hello, World! " — capitalizes each word
"hello world".capitalize() # "Hello world" — capitalizes first char only
# Search and test — useful for input validation and parsing
"Python".startswith("Py") # True
"Python".endswith("on") # True
"hello world".find("world") # 6 — returns -1 if not found
"hello world".index("world") # 6 — raises ValueError if not found
"aababc".count("a") # 3 — count non-overlapping occurrences
# Replace and split
"hello world".replace("world", "Python") # "hello Python"
"a,b,,c".split(",") # ["a", "b", "", "c"]
"a,b,,c".split(",", maxsplit=2) # ["a", "b", ",c"]
" words with spaces ".split() # ["words", "with", "spaces"] — splits on any whitespace
# Join — the inverse of split; much faster than + concatenation in a loop
", ".join(["Alice", "Bob", "Carol"]) # "Alice, Bob, Carol"
"".join(["P", "y", "t", "h", "o", "n"]) # "Python"
# Character type tests — useful for validating user input
"abc123".isalnum() # True — all alphanumeric
"abc".isalpha() # True — all alphabetic
"123".isdigit() # True — all digits
" ".isspace() # True — all whitespace
f-Strings (Formatted String Literals)
f-strings are Python’s preferred string formatting mechanism. They’re faster than % formatting and str.format(), they support any Python expression inside {}, and they read naturally alongside the surrounding code.
name = "Alice"
age = 30
balance = 1234.567
# Basic variable interpolation
f"Hello, {name}!"
# Any Python expression works inside {}
f"Next year you'll be {age + 1}"
# Format specifiers control number formatting, alignment, and precision
f"Balance: ${balance:.2f}" # "Balance: $1234.57" — 2 decimal places
f"Percentage: {0.1234:.1%}" # "Percentage: 12.3%" — percent format
f"Padded: {name:>10}" # " Alice" — right-align, width 10
f"Padded: {name:<10}" # "Alice " — left-align
f"Padded: {name:^10}" # " Alice " — center
f"Zero-padded: {42:05d}" # "00042" — zero-fill to width 5
f"Hex: {255:#x}" # "0xff" — hex with prefix
f"Sci: {1234567.89:.2e}" # "1.23e+06" — scientific notation
# Self-documenting expressions (Python 3.8+) — invaluable for debugging
value = 42
f"{value=}" # "value=42" — prints both the expression and its result
# Multiline f-strings — parentheses allow line continuation
message = (
f"Name: {name}\n"
f"Age: {age}\n"
f"Balance: ${balance:.2f}"
)
str.format()
str.format() is useful when the template string is dynamic — loaded from a config file, database, or user input — since you can’t use f-strings with runtime-determined templates.
template = "Hello, {name}! You have {count} messages."
template.format(name="Alice", count=5)
# "Hello, Alice! You have 5 messages."
# Positional placeholders
"{0} + {1} = {2}".format(1, 2, 3) # "1 + 2 = 3"
# Reuse a positional argument multiple times
"{0} {0} {1}".format("echo", "end") # "echo echo end"
Building Strings Efficiently
Because strings are immutable, every + concatenation creates a new string object. In a loop, this produces O(n²) allocations — slow for large data. The fix is to collect parts in a list and join once at the end.
# Slow — a new string object is allocated on every iteration
result = ""
for word in words:
result += word + " "
# Fast — one allocation at the end
result = " ".join(words)
# Also good — collect parts, join once
parts = []
for item in items:
parts.append(str(item))
output = ", ".join(parts)
String Encoding
Python 3 strings are Unicode by default — str holds text, bytes holds raw binary data. Encoding converts text to bytes for I/O (files, network sockets), and decoding converts bytes back to text. Always specify utf-8 explicitly rather than relying on the platform default.
# Encode text to bytes for writing to a file or sending over a socket
s = "Hello, 世界"
b = s.encode("utf-8") # b'Hello, \xe4\xb8\x96\xe7\x95\x8c'
# Decode bytes back to a string after reading from I/O
b.decode("utf-8") # "Hello, 世界"
import sys
sys.getdefaultencoding() # "utf-8"
Regex Basics with re
Regular expressions let you search, extract, and transform text based on patterns rather than exact strings. They’re the right tool when str.find() or str.replace() isn’t expressive enough — parsing emails, phone numbers, log lines, or structured text.
import re
text = "Contact us at [email protected] or [email protected]"
# Search for the first match anywhere in the string
match = re.search(r"\b[\w.]+@[\w.]+\.\w+\b", text)
if match:
print(match.group()) # "[email protected]"
# Find all non-overlapping matches
emails = re.findall(r"\b[\w.]+@[\w.]+\.\w+\b", text)
# ["[email protected]", "[email protected]"]
# Substitute matches with a replacement string
clean = re.sub(r"\d+", "NUM", "I have 3 cats and 12 dogs")
# "I have NUM cats and NUM dogs"
# Split on a pattern rather than a fixed string
parts = re.split(r"[,\s]+", "one, two, three")
# ["one", "two", "three"]
# Compile a pattern when using it multiple times — avoids recompiling each call
email_re = re.compile(r"\b[\w.]+@[\w.]+\.\w+\b")
email_re.findall(text)
Common Regex Patterns
# Phone number (international format)
re.match(r"^\+?1?\d{9,15}$", "+14155552671")
# URL
re.match(r"https?://[^\s]+", "https://example.com/path")
# ISO date
re.match(r"^\d{4}-\d{2}-\d{2}$", "2024-01-15")
# Named groups — access captured substrings by name, not position
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", "2024-01-15")
m.group("year") # "2024"
m.group("month") # "01"
textwrap
The textwrap module handles two common formatting tasks: wrapping long strings to a fixed column width, and removing consistent leading indentation from multi-line strings (useful when writing multi-line strings inside indented code blocks).
import textwrap
long_text = "This is a very long line that needs to be wrapped at a reasonable column width."
# Wrap to 40 characters per line
print(textwrap.fill(long_text, width=40))
# Remove consistent leading whitespace — useful for embedded SQL or HTML
query = textwrap.dedent("""
SELECT *
FROM users
WHERE active = true
""").strip()