Python String Methods: Essential Guide with Examples
Python string methods are built-in functions you call on a string, such as text.strip() or text.split(","), to clean, split, search, test and pad text. Strings are immutable, so every method returns a new value and leaves the original string unchanged.
Quick answer: The string methods you will use most are strip() to trim whitespace, lower() and upper() to change case, split() and join() to break text apart and rebuild it, replace() to substitute text, find() to locate a substring, startswith() and endswith() to check prefixes and suffixes, and isdigit() to test content. Each returns a new value, so assign it: s = s.strip().
How do you change case with upper(), lower() and title()?
upper(), lower(), title(), capitalize() and swapcase() return a copy of the string with the letter case changed. Lowercase both sides before comparing user-entered text, or use casefold(), a stricter version of lower() built for case-insensitive matching.
text = "Hello World"
# Convert to uppercase
text.upper() # "HELLO WORLD"
# Convert to lowercase
text.lower() # "hello world"
# Title case (capitalize each word)
text.title() # "Hello World"
"hello world".title() # "Hello World"
# Capitalize first letter only
"hello world".capitalize() # "Hello world"
# Swap case
"Hello World".swapcase() # "hELLO wORLD"
Watch out for title() with apostrophes: "they're bill's".title() returns "They'Re Bill'S" because it capitalizes every letter that follows a non-letter. string.capwords("they're bill's") splits on whitespace instead and returns "They're Bill's".
How do you remove whitespace with strip()?
strip() removes leading and trailing whitespace, while lstrip() and rstrip() trim only the left or right side. Pass an argument to remove a set of characters instead of whitespace, as in "###Hello###".strip("#").
# Remove leading and trailing whitespace โ
text = " hello world "
text.strip() # "hello world"
# Remove only from left
text.lstrip() # "hello world "
# Remove only from right
text.rstrip() # " hello world"
# Remove specific characters
"###Hello###".strip('#') # "Hello"
"...python...".strip('.') # "python"
# Common use: Clean user input
user_input = input().strip() # Remove extra spaces
How do split() and join() work?
split() breaks a string into a list on a separator, and join() does the reverse: it glues a list of strings together using the string you call it on as the separator, as in ", ".join(names).
# Split string into list
text = "apple,banana,cherry"
fruits = text.split(',') # ['apple', 'banana', 'cherry']
# Split on whitespace (default)
"hello world python".split() # ['hello', 'world', 'python']
# Limit splits
"a-b-c-d".split('-', 2) # ['a', 'b', 'c-d']
# Split lines
multiline = "line1\nline2\nline3"
lines = multiline.splitlines() # ['line1', 'line2', 'line3']
# Join list into string โ
words = ['hello', 'world']
' '.join(words) # "hello world"
','.join(words) # "hello,world"
'\n'.join(words) # "hello\nworld"
Calling split() with no argument is not the same as split(" "). The default treats any run of whitespace as one separator and ignores leading and trailing whitespace, while "a b".split(" ") returns ['a', '', 'b']. join() also needs every item to be a string, so convert numbers first with ",".join(map(str, numbers)).
replace() - Substitute Text
replace(old, new) returns a copy with every occurrence of old swapped for new, or only the first n when you pass a count as the third argument. Replace with an empty string to delete text, and use regular expressions when you need to match a pattern rather than fixed text.
# Replace substring
text = "Hello World"
text.replace('World', 'Python') # "Hello Python"
# Replace all occurrences
text = "apple apple apple"
text.replace('apple', 'orange') # "orange orange orange"
# Limit replacements
text.replace('apple', 'orange', 2) # "orange orange apple"
# Remove characters (replace with empty string)
"hello123world".replace('123', '') # "helloworld"
# Chain replacements
text = "a-b-c"
text.replace('-', ' ').replace(' ', '_') # "a_b_c"
find() and index() - Search
find() returns the position of the first match or -1 if there is none, while index() raises ValueError instead. Use the in operator when you only need to know whether a substring exists, and count() to count non-overlapping occurrences.
text = "Hello World"
# find() - returns index or -1 if not found โ
text.find('World') # 6
text.find('xyz') # -1
# index() - returns index or raises ValueError
text.index('World') # 6
# text.index('xyz') # ValueError!
# Find from right
text.rfind('o') # 7 (last 'o')
text.rindex('o') # 7
# Check if substring exists (better than find)
if 'World' in text:
print("Found!")
# Count occurrences
"hello world".count('l') # 3
"banana".count('na') # 2
startswith() and endswith()
startswith() and endswith() return True or False for a prefix or suffix check, and both accept a tuple to test several options in one call.
# Check prefix
filename = "report.pdf"
filename.startswith('report') # True
filename.startswith('data') # False
# Check suffix
filename.endswith('.pdf') # True
filename.endswith('.csv') # False
# Multiple options (tuple)
filename.endswith(('.pdf', '.doc', '.txt')) # True
# Practical: Filter files
files = ['data.csv', 'image.png', 'report.pdf']
csv_files = [f for f in files if f.endswith('.csv')]
# Check URL
url = "https://example.com"
if url.startswith(('http://', 'https://')):
print("Valid URL")
String Formatting
F-strings are the modern way to insert values into text: prefix the string with f and put variables or expressions in braces. str.format() and % formatting still work and are common in older code; see f-string formatting for dates, decimals and alignment.
# f-strings (Python 3.6+) โ
Best!
name = "Alice"
age = 30
print(f"{name} is {age} years old")
# "Alice is 30 years old"
# Format with expressions
print(f"{name.upper()} is {age + 1} next year")
# Format numbers
price = 19.99
print(f"Price: ${price:.2f}") # "Price: $19.99"
# .format() method
"Hello {}, you are {}".format(name, age)
"Hello {name}, age {age}".format(name="Bob", age=25)
# % formatting (old style)
"Hello %s, you are %d" % (name, age)
Checking String Content
Checks such as isdigit(), isalpha() and isspace() return True only when the string is non-empty and every character passes the test. That is why "123.45".isdigit() is False: the decimal point is not a digit.
# Check if alphanumeric
"Hello123".isalnum() # True
"Hello 123".isalnum() # False (space)
# Check if alphabetic
"Hello".isalpha() # True
"Hello123".isalpha() # False
# Check if numeric
"12345".isdigit() # True
"123.45".isdigit() # False
# Check if lowercase/uppercase
"hello".islower() # True
"HELLO".isupper() # True
# Check if whitespace
" ".isspace() # True
" a ".isspace() # False
Padding and Alignment
center(), ljust() and rjust() pad a string to a fixed width with spaces or a fill character you choose, and zfill() pads with zeros after any leading sign. A string already at or beyond the width comes back unchanged.
# Center text
"hello".center(10) # " hello "
"hello".center(10, '*') # "**hello***"
# Left justify
"hello".ljust(10) # "hello "
"hello".ljust(10, '-') # "hello-----"
# Right justify
"hello".rjust(10) # " hello"
"hello".rjust(10, '0') # "00000hello"
# Zero padding for numbers
"42".zfill(5) # "00042"
"-42".zfill(5) # "-0042"
Practical Examples
Real cleanup jobs chain these methods: strip().lower() normalizes an email address, split(",") unpacks a CSV line, and split("=") inside a list comprehension turns key-value pairs into a dictionary.
# Clean and normalize email
email = " User@Example.COM "
clean_email = email.strip().lower() # "user@example.com"
# Parse CSV line
line = "John,25,NYC"
name, age, city = line.split(',')
# Extract filename from path
path = "/home/user/documents/file.txt"
filename = path.split('/')[-1] # "file.txt"
basename = filename.split('.')[0] # "file"
# Create slug from title
title = "Hello World - Python Tutorial!"
slug = title.lower().replace(' ', '-').replace('!', '')
# "hello-world---python-tutorial"
# Validate password
password = "MyPassword123"
is_valid = (len(password) >= 8 and
any(c.isupper() for c in password) and
any(c.isdigit() for c in password))
# Parse key-value pairs
config = "name=John;age=30;city=NYC"
pairs = [pair.split('=') for pair in config.split(';')]
config_dict = dict(pairs)
# {'name': 'John', 'age': '30', 'city': 'NYC'}
Why doesn't my string change after calling a method?
Because strings are immutable: name.strip() returns a cleaned copy and leaves name exactly as it was. Assign the result back to the same variable or to a new one.
name = " Alice "
name.strip() # returns "Alice", but the result is thrown away
print(repr(name)) # ' Alice '
name = name.strip() # reassign to keep the result
print(repr(name)) # 'Alice'
How do you remove a prefix or suffix from a string?
Use removeprefix() and removesuffix() (Python 3.9+), which remove an exact substring from one end only when it is there. Don't use lstrip() or rstrip() for this: their argument is a set of characters, so they keep removing any of those characters.
"stats.csv".rstrip(".csv") # "stat" (the final s was removed too)
"stats.csv".removesuffix(".csv") # "stats"
"www.example.com".removeprefix("www.") # "example.com"
"example.com".removeprefix("www.") # "example.com" (no prefix, unchanged)
How do you check if a string is a number?
isdigit() returns True only when every character is a digit, so it rejects "-5", "3.14", " 42 " and the empty string. To test whether text parses as a number, try float() and catch ValueError; note that float() also accepts "nan" and "inf".
"42".isdigit() # True
"-5".isdigit() # False (minus sign)
"3.14".isdigit() # False (decimal point)
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
is_number("-5") # True
is_number(" 3.14 ") # True (float() ignores surrounding whitespace)
is_number("abc") # False
How do you apply string methods to a pandas column?
Use the .str accessor, which runs the method on every value in a Series and leaves missing values missing instead of raising an error. For example, df["email"].str.strip().str.lower() cleans a whole column in one line.
import pandas as pd
df = pd.DataFrame({"email": [" User@Example.COM ", "bob@test.io "]})
df["email"] = df["email"].str.strip().str.lower()
print(df["email"].tolist())
# ['user@example.com', 'bob@test.io']
Best Practices
Prefer f-strings for formatting, the in operator for membership tests and join() for assembling many pieces, and always assign a method's result because strings never change in place.
- โ Use f-strings for formatting (Python 3.6+) - fastest and most readable
- โ
Use
strip()on user input to remove extra whitespace - โ
Use
inoperator instead offind() != -1 - โ
Use
join()to concatenate many strings (much faster than +) - โ
Use
startswith()/endswith()for prefix/suffix checks - โ Don't use + in loops to build strings (use list + join instead)
- โ Don't use
index()without try/except (usefind()instead) - โ ๏ธ Remember: strings are immutable - methods return new strings
Pro Tip: When building a string from many parts in a loop, collect them in a list and use join() at the end. Each + builds a new string, so repeated concatenation can slow down as the text grows. CPython often optimizes s += piece in place, but PEP 8 advises against relying on that.