Home Tutorials Core Syntax

Core Syntax

Python String Methods: A Practical Reference for Everyday Text Processing

Pyford Notes July 1, 2026 7 min read
Key points
  • Strings are immutable; every method returns a new string and leaves the original unchanged.
  • find() returns -1 on failure; index() raises ValueError — choose based on whether absence is an error.
  • "sep".join(iterable) is faster than repeated + concatenation for building strings from a list.
  • str.translate(table) with str.maketrans() handles bulk character replacements in a single pass.

Searching: find, index, count, startswith, endswith, in

MethodReturnsOn failure
find(sub)Lowest index of sub-1
rfind(sub)Highest index of sub-1
index(sub)Lowest index of subValueError
rindex(sub)Highest index of subValueError
count(sub)Number of non-overlapping occurrences0
startswith(prefix)True / FalseFalse
endswith(suffix)True / FalseFalse
text = "the quick brown fox jumps over the lazy dog"

print(text.find("fox"))          # 16
print(text.find("cat"))          # -1
print(text.count("the"))         # 2
print(text.startswith("the"))    # True
print(text.endswith("cat"))      # False

# in operator: cleanest membership test
print("fox" in text)             # True

Use find() when a missing substring is a normal case (such as optional URL parameters). Use index() when absence would be a programming error worth raising an exception over.

Both startswith and endswith accept a tuple of prefixes/suffixes to test against multiple options at once:

filename = "report.pdf"
print(filename.endswith((".pdf", ".PDF")))   # True

Splitting and joining: split, rsplit, partition, join

MethodDescription
split(sep, maxsplit)Split from the left on sep (default: any whitespace)
rsplit(sep, maxsplit)Split from the right
splitlines()Split on line endings; handles CR, LF, and CRLF
partition(sep)Returns (before, sep, after) — exactly 3 parts
join(iterable)Join an iterable of strings with self as separator
line = "name=Alice,age=30,city=London"

parts = line.split(",")
# ['name=Alice', 'age=30', 'city=London']

key, sep, value = "name=Alice".partition("=")
# key='name', sep='=', value='Alice'

words = ["Python", "is", "great"]
sentence = " ".join(words)
# 'Python is great'

# rsplit with maxsplit: useful for parsing paths
path = "/home/user/docs/report.pdf"
head, _, tail = path.rpartition("/")
# head='/home/user/docs', tail='report.pdf'

Building a long string by concatenating with + inside a loop creates a new string object on every iteration. Collect parts into a list first and join at the end — it is significantly faster for large numbers of pieces.

Stripping whitespace and characters: strip, lstrip, rstrip

MethodEffect
strip(chars)Remove leading and trailing chars (default: whitespace)
lstrip(chars)Remove leading chars only
rstrip(chars)Remove trailing chars only
removeprefix(prefix)Remove exact prefix if present (Python 3.9+)
removesuffix(suffix)Remove exact suffix if present (Python 3.9+)
raw = "   hello world   "
print(raw.strip())          # "hello world"
print(raw.lstrip())         # "hello world   "
print(raw.rstrip())         # "   hello world"

# chars argument: strips any combination of those characters
messy = "...!!hello!...!!!"
print(messy.strip(".!"))    # "hello"

# removeprefix / removesuffix (Python 3.9+)
url = "https://example.com"
print(url.removeprefix("https://"))    # "example.com"
filename = "report.pdf"
print(filename.removesuffix(".pdf"))   # "report"

Note that strip(chars) removes any combination of the listed characters, not the literal substring. Use removeprefix / removesuffix when you need to match an exact sequence.

Replacing: replace, translate

text = "aabbccaabb"

print(text.replace("aa", "X"))          # "XbbccXbb"
print(text.replace("aa", "X", 1))       # "Xbbccaabb" (max 1 replacement)

replace is straightforward for simple substitutions. For bulk single-character replacements, translate with maketrans is much faster because it operates in a single pass over the string:

table = str.maketrans("aeiou", "AEIOU")
print("hello world".translate(table))
# "hEllO wOrld"

# Delete characters by passing None as the mapping value
delete_digits = str.maketrans("", "", "0123456789")
print("ph0n3 numb3r".translate(delete_digits))
# "phn numbr"

Case conversion: upper, lower, title, capitalize, swapcase

MethodExample inputExample output
upper()hello worldHELLO WORLD
lower()HELLO WORLDhello world
capitalize()hello worldHello world
title()hello worldHello World
swapcase()Hello WorldhELLO wORLD
casefold()HELLOhello (Unicode-safe)
s = "python is FUN"
print(s.upper())       # "PYTHON IS FUN"
print(s.lower())       # "python is fun"
print(s.title())       # "Python Is Fun"
print(s.capitalize())  # "Python is fun"
print(s.swapcase())    # "PYTHON IS fun"

Use casefold() rather than lower() for case-insensitive comparisons involving non-ASCII text. For example, the German sharp-s character ss casefolded is ss, which lower() does not handle correctly on all platforms.

Testing content: isalpha, isdigit, isalnum, isspace, isidentifier

MethodReturns True if all characters are…
isalpha()Alphabetic (letters only)
isdigit()Digits (0–9 and Unicode digits)
isnumeric()Numeric (includes fractions, superscripts)
isalnum()Alphabetic or digit
isspace()Whitespace characters
isidentifier()A valid Python identifier
isupper()All cased characters are uppercase
islower()All cased characters are lowercase
istitle()Title-cased (each word starts capitalised)
print("hello".isalpha())        # True
print("hello123".isalpha())     # False
print("123".isdigit())          # True
print("hello123".isalnum())     # True
print("  \t\n".isspace())       # True
print("my_var".isidentifier())  # True
print("2bad".isidentifier())    # False

All testing methods return False on an empty string. Always guard against empty input before relying on these methods for validation.

Method chaining and immutability

Because every string method returns a new string object, you can chain calls in a single expression:

raw = "  The Quick Brown Fox  "
result = raw.strip().lower().replace("fox", "cat")
print(result)   # "the quick brown cat"

The original string raw is never modified. This immutability is fundamental to how Python strings work: they are interned and shared under the hood, so mutation would be unsafe. Every method call allocates a new string. For a very long chain of transformations on large strings, consider whether a single translate call or a compiled regular expression would be more efficient.

original = "hello"
modified = original.upper()
print(original)   # "hello"  -- unchanged
print(modified)   # "HELLO"  -- new object
print(original is modified)   # False