String Manipulation & Formatting
Master string operations, slicing, and modern formatting techniques.
Introduction
Strings are everywhere in programming, from user input to file processing to web scraping. Mastering string manipulation is essential for building real-world applications. In this lesson, you'll learn how to slice strings like a pro, format output beautifully with f-strings, and use powerful built-in methods to transform and validate text data. These skills form the foundation for working with files, APIs, and data processing, skills you'll use in every Python project.
Understanding Strings
Strings are sequences of characters. Each character has a position (called an index), making strings similar to lists but for text data. Understanding this concept is key to mastering string manipulation.
text = "Python"
# A string is a sequence: P-y-t-h-o-n
# 0 1 2 3 4 5 (positive indices)
# -6-5-4-3-2-1 (negative indices)
# Get the length of a string
print(len(text))
# Strings are immutable (cannot be changed after creation)
try:
text[0] = "J"
except TypeError as e:
print(f"TypeError: {e}")
# But you can create NEW strings based on existing ones
new_text = "J" + text[1:]
print(new_text)Expected Output:
6 TypeError: 'str' object does not support item assignment Jython
String Indexing & Slicing
Access individual characters or extract substrings using indices and slicing. This is one of the most powerful features for string manipulation.
Basic Indexing
Access individual characters using square brackets and an index. Python uses 0-based indexing (the first character is at index 0).
text = "Python"
# Positive indexing (0-based, from left)
print(text[0])
print(text[1])
print(text[5])
# Negative indexing (from right)
print(text[-1]) # last character
print(text[-2])
print(text[-6]) # same as text[0]
# IndexError if out of range
try:
text[10]
except IndexError as e:
print(f"IndexError: {e}")Expected Output:
P y n n o P IndexError: string index out of range
Positive and negative indices for text = "Python"
Green = positive index (counting from the left, starting at 0). Red = negative index (counting from the right, starting at -1). So text[0] and text[-6] both give "P".
Slicing Syntax
Extract portions of strings using [start:end:step] syntax. The end index is exclusive (not included in the result).
text = "Python Programming" # 0123456789... # Basic slicing [start:end] (end is exclusive) print(text[0:6]) print(text[7:18]) # Omit start or end print(text[:6]) # start defaults to 0 print(text[7:]) # end defaults to length print(text[:]) # full copy # Negative indices in slicing print(text[-11:]) # last 11 characters print(text[:-12]) # everything except last 12
Expected Output:
Python Programming Python Programming Python Programming Programming Python
Step Parameter
The optional step parameter controls which characters to include. A negative step reverses the string!
text = "Python Programming" # Step parameter [start:end:step] print(text[::2]) # every 2nd character print(text[::3]) # every 3rd character print(text[1::2]) # start at index 1, every 2nd # Reverse string with negative step print(text[::-1]) # reverse entire string print(text[::-2]) # reverse, every 2nd char # Combine start, end, and step print(text[7::2]) # start at 7, every 2nd char
Expected Output:
Pto rgamn Ph oai yhnPormig gnimmargorP nohtyP gimroPnhy Pormig
Common Slicing Patterns
Here are practical slicing patterns you'll use frequently in real projects.
text = "Hello, World!" # First n characters print(text[:5]) print(text[:3]) # Last n characters print(text[-6:]) print(text[-1:]) # Remove first n characters print(text[7:]) # skip first 7 print(text[2:]) # skip first 2 # Remove last n characters print(text[:-7]) # remove last 7 print(text[:-1]) # remove last 1 # Reverse a string print(text[::-1]) # Every other character print(text[::2]) # Middle portion print(text[2:10])
Expected Output:
Hello Hel World! ! World! llo, World! Hello, Hello, World !dlroW ,olleH Hlo ol! llo, Wor
Remember: Slicing never modifies the original string. It always creates a new string. Strings are immutable in Python, once created, they cannot be changed.
String Concatenation & Repetition
Combine strings together or repeat them multiple times using operators.
# Concatenation (joining strings with +)
first = "Hello"
last = "World"
print(first + " " + last)
# Concatenation with variables
name = "Alice"
print("Hello, " + name + "!")
# Repetition (repeating strings with *)
print("*" * 5)
print("-" * 20)
print("=" * 10)
# Practical use cases
print("+" + "-" * 10 + "+")
print("!" * 3 + " ALERT " + "!" * 3)
# Combine concatenation and repetition
print("=" * 5 + " TITLE " + "=" * 5)
# Multiple lines
divider = "-" * 40
print(divider)
print("Important Information")
print(divider)Expected Output:
Hello World Hello, Alice! ***** -------------------- ========== +----------+ !!! ALERT !!! ===== TITLE ===== ---------------------------------------- Important Information ----------------------------------------
Performance Note: While + works for concatenation, it's not the most efficient for combining many strings. For multiple strings, use f-strings or the .join() method (covered later).
String Formatting
Python offers multiple ways to format strings. F-strings (formatted string literals) are the modern, recommended approach introduced in Python 3.6+.
F-Strings (Recommended)
F-strings are the most readable and fastest way to format strings. Prefix your string with f and use curly braces{} to embed expressions.
name = "Alice"
age = 30
# Basic f-string
print(f"My name is {name}")
# Multiple variables
print(f"I'm {name}, {age} years old")
# Expressions inside f-strings
print(f"Next year I'll be {age + 1}")
print(f"2 + 2 = {2 + 2}")
# Method calls inside f-strings
print(f"Hello, {name.upper()}!")
# Formatting numbers
price = 19.99
print(f"Price: ${price:.2f}")
percentage = 0.156
print(f"Score: {percentage:.1%}")Expected Output:
My name is Alice I'm Alice, 30 years old Next year I'll be 31 2 + 2 = 4 Hello, ALICE! Price: $19.99 Score: 15.6%
Format Specifiers
Control how values are displayed using format specifiers after a colon. This gives you precise control over alignment, padding, and number formatting.
# Decimal places for floats
pi = 3.14159
print(f"{pi:.2f}") # 2 decimal places
print(f"{pi:.4f}") # 4 decimal places
print(f"{pi:.0f}") # no decimal places, rounds
# Padding and alignment (the pipes make the padding visible)
name = "Bob"
print(f"|{name:>10}|") # right align, width 10
print(f"|{name:<10}|") # left align, width 10
print(f"|{name:^10}|") # center, width 10
print(f"|{name:*^10}|") # center with * padding
# Numbers with padding
num = 42
print(f"|{num:05d}|") # pad with zeros, width 5
print(f"|{num:5d}|") # pad with spaces, width 5
# Thousands separator
print(f"{1000000:,}")
print(f"{1234567.89:,.2f}")
# Percentage formatting
ratio = 0.75
print(f"{ratio:.1%}")
print(f"{ratio:.0%}")
# Binary, octal, hexadecimal
print(f"{num:b}") # binary
print(f"{num:o}") # octal
print(f"{num:x}") # hexadecimal
print(f"{num:X}") # uppercase hexExpected Output:
3.14 3.1416 3 | Bob| |Bob | | Bob | |***Bob****| |00042| | 42| 1,000,000 1,234,567.89 75.0% 75% 101010 52 2a 2A
.format() Method
The older .format() method is still widely used, especially in older codebases or when you need reusable format strings.
name = "Alice"
age = 30
# Positional arguments
print("My name is {} and I'm {} years old".format(name, age))
# Named arguments
print("My name is {n} and I'm {a} years old".format(n=name, a=age))
# Index-based (reuse arguments)
print("{0} is {1} years old. {0} is a developer.".format(name, age))
# With format specifiers
print("Price: ${:.2f}".format(19.99))
print("{:<10} | {:>10}".format("Name", "Score"))
# Reusable format strings
template = "Hello, {name}! Your score is {score:.1f}."
print(template.format(name="Bob", score=95.6))
print(template.format(name="Alice", score=87.3))Expected Output:
My name is Alice and I'm 30 years old My name is Alice and I'm 30 years old Alice is 30 years old. Alice is a developer. Price: $19.99 Name | Score Hello, Bob! Your score is 95.6. Hello, Alice! Your score is 87.3.
% Formatting (Legacy)
Old-style formatting using the % operator. Still found in older code, but use f-strings in new projects!
name = "Alice"
age = 30
# Old style (C-like formatting)
print("My name is %s and I'm %d years old" % (name, age))
# Format specifiers:
# %s = string
# %d = integer
# %f = float
# %.<precision>f = float with decimal places
print("Price: $%.2f" % 19.99)
print("Score: %d%%" % 85) # need %% for a literal %
# With padding
print("%-10s | %10s" % ("Name", "Score"))Expected Output:
My name is Alice and I'm 30 years old Price: $19.99 Score: 85% Name | Score
Comparison: Which One to Use?
✓ F-Strings
- Most readable
- Fastest performance
- Concise syntax
- Python 3.6+
- Recommended!
.format()
- Good for templates
- Python 2.7+
- Reusable format strings
- Still widely used
- Slightly more verbose
% Formatting
- Legacy method
- Harder to read
- Limited features
- C-style syntax
- Avoid in new code
Essential String Methods
Python provides many built-in methods to manipulate and analyze strings. Here are the essential ones organized by category.
Case Transformation
Methods to change the case of characters in strings.
text = "hello, World!"
print(text.upper())
print(text.lower())
print(text.capitalize()) # first letter only
print(text.title()) # each word capitalized
print(text.swapcase()) # swap case
# Practical use cases
email = "Alice.Smith@EXAMPLE.com"
print(email.lower())
title = "the lord of the rings"
print(title.title())
# Check case
print("HELLO".isupper())
print("hello".islower())
print("Hello World".istitle())Expected Output:
HELLO, WORLD! hello, world! Hello, world! Hello, World! HELLO, wORLD! alice.smith@example.com The Lord Of The Rings True True True
Whitespace Handling
Remove or manage whitespace from strings, essential for cleaning user input.
text = " Hello, World! "
# repr() shows the quotes, so you can see exactly which spaces survive
print(repr(text.strip())) # both ends
print(repr(text.lstrip())) # left only
print(repr(text.rstrip())) # right only
# Remove specific characters
print(repr("...Hello...".strip(".")))
print(repr("---555-1234---".strip("-")))
# Practical use case: cleaning user input
user_input = " alice@example.com "
print(repr(user_input.strip()))
# Check for whitespace
print(" ".isspace()) # all whitespace
print("abc".isspace())
print("".isspace()) # empty stringExpected Output:
'Hello, World!' 'Hello, World! ' ' Hello, World!' 'Hello' '555-1234' 'alice@example.com' True False False
Searching & Finding
Search for substrings and get information about their location.
text = "Hello, World! Hello, Python!"
# Find substring (returns index or -1 if not found)
print(text.find("World"))
print(text.find("Python"))
print(text.find("Java")) # not found
print(text.find("Hello", 1)) # search starting at index 1
# Index (like find, but raises ValueError if not found)
print(text.index("World"))
try:
text.index("Java")
except ValueError as e:
print(f"ValueError: {e}")
# Count occurrences
print(text.count("Hello"))
print(text.count("o"))
print(text.count("z"))
# Check start/end
print(text.startswith("Hello"))
print(text.startswith("Hi"))
print(text.endswith("Python!"))
print(text.endswith("World"))
# Check if substring exists (using 'in')
print("World" in text)
print("Java" in text)
# Case-insensitive search
print(text.lower().find("hello"))
print("hello" in text.lower())Expected Output:
7 21 -1 14 7 ValueError: substring not found 2 4 0 True False True False True False 0 True
String Modification
Transform strings by replacing, splitting, or joining content.
text = "Hello, World!"
# Replace substring
print(text.replace("World", "Python"))
print(text.replace("l", "L"))
print(text.replace("l", "L", 2)) # max 2 replacements
# Remove substring (replace with empty string)
print(text.replace("Hello, ", ""))
# Split into list
print(text.split(","))
print("a-b-c".split("-"))
print("one two three".split()) # split on any whitespace
csv = "Alice,30,Engineer"
name, age, job = csv.split(",") # unpack directly
print(name, age, job)
# Join list into string
words = ["Hello", "World"]
print(" ".join(words))
print("-".join(words))
print("".join(words))
# Join with newlines
lines = ["Line 1", "Line 2", "Line 3"]
print("\n".join(lines))
# Practical: build CSV
data = ["Alice", "30", "Engineer"]
print(",".join(data))Expected Output:
Hello, Python! HeLLo, WorLd! HeLLo, World! World! ['Hello', ' World!'] ['a', 'b', 'c'] ['one', 'two', 'three'] Alice 30 Engineer Hello World Hello-World HelloWorld Line 1 Line 2 Line 3 Alice,30,Engineer
Important: String methods don't modify the original string (strings are immutable). They return a new string with the changes. Always assign the result to a variable if you want to keep it.
String Validation
Check the content and characteristics of strings.
# Check string content type
print("123".isdigit()) # all digits
print("abc".isalpha()) # all letters
print("abc123".isalnum()) # letters or digits
print(" ".isspace()) # all whitespace
print("".isspace()) # empty string
print("Hello123".isdigit()) # has letters
print("Hello World".isalpha()) # has space
# Check case
print("Hello".istitle())
print("HeLLo".istitle())
# Practical validation examples
age_input = "25"
if age_input.isdigit():
age = int(age_input)
print(f"Age: {age}")
else:
print("Invalid age - must be digits only")
username = "alice123"
if username.isalnum():
print("Valid username")
else:
print("Username must be alphanumeric")
# Other useful checks
print("abc".isascii()) # all ASCII characters
print("123.45".isdecimal()) # the dot is not a decimal digit
print("Hello".isprintable())Expected Output:
True True True True False False False True False Age: 25 Valid username True False True
Practical String Examples
Now let's combine what we've learned to solve real-world problems. These examples show how string methods work together in practical scenarios.
Example 1: Email Validation & Formatting
Clean and validate email input by combining multiple string methods.
# Clean and validate email input
email = " Alice.Smith@EXAMPLE.COM "
# Step 1: Remove whitespace
email = email.strip() # "Alice.Smith@EXAMPLE.COM"
# Step 2: Convert to lowercase (emails are case-insensitive)
email = email.lower() # "alice.smith@example.com"
# Step 3: Validate structure
has_at = "@" in email
only_one_at = email.count("@") == 1
# Check domain has a dot
domain = email.split("@")[1] if has_at else ""
has_dot_in_domain = "." in domain
if has_at and only_one_at and has_dot_in_domain:
print(f"Valid email: {email}")
# Extract parts
username, domain = email.split("@")
print(f"Username: {username}")
print(f"Domain: {domain}")
# Check domain
if domain.endswith(".edu"):
print("Educational institution")
elif domain.endswith(".com"):
print("Commercial domain")
else:
print("Invalid email format")
# Chain all cleaning steps
def clean_email(email):
return email.strip().lower()
print(clean_email(" BOB@EXAMPLE.COM "))Expected Output:
Valid email: alice.smith@example.com Username: alice.smith Domain: example.com Commercial domain bob@example.com
Example 2: Name Formatting
Format names consistently by handling extra whitespace and proper capitalization.
# Format names consistently
full_name = " alice marie SMITH "
# Step 1: Strip outer whitespace
full_name = full_name.strip() # "alice marie SMITH"
# Step 2: Split by whitespace (handles multiple spaces)
parts = full_name.split() # ["alice", "marie", "SMITH"]
# Step 3: Capitalize each part
parts = [part.capitalize() for part in parts]
# ["Alice", "Marie", "Smith"]
# Step 4: Join back together
formatted = " ".join(parts)
print(f"Formatted name: {formatted}")
# As a reusable function
def format_name(name):
"""Format a name with proper capitalization and spacing."""
return " ".join(part.capitalize() for part in name.strip().split())
# Test the function
print(format_name(" john DOE "))
print(format_name("mary-jane watson")) # capitalize() lowercases the rest
# Handle different name formats
def get_initials(name):
"""Get initials from a name."""
parts = name.strip().split()
initials = [part[0].upper() for part in parts]
return ".".join(initials) + "."
print(get_initials("Alice Marie Smith"))
print(get_initials("john doe"))Expected Output:
Formatted name: Alice Marie Smith John Doe Mary-jane Watson A.M.S. J.D.
Example 3: Creating a Text Report
Generate formatted reports with alignment and number formatting.
# Generate a formatted sales report
name = "Alice"
items_sold = 47
revenue = 2350.75
target = 2000
# Create report with formatting
header = "Sales Report".center(40, "=")
separator = "=" * 40
report = f"""
{header}
{separator}
Salesperson: {name}
Items Sold: {items_sold:>10}
Revenue: ${revenue:>10,.2f}
Target: ${target:>10,.2f}
Performance: {(revenue/target):>10.1%}
{separator}
Status: {"EXCEEDS TARGET" if revenue > target else "BELOW TARGET"}
"""
print(report)Expected Output:
==============Sales Report============== ======================================== Salesperson: Alice Items Sold: 47 Revenue: $ 2,350.75 Target: $ 2,000.00 Performance: 117.5% ======================================== Status: EXCEEDS TARGET
Watch the currency column: the padding landsbetween the $ and the number. The$ is a literal character sitting before the field, so{revenue:>10,.2f} right-aligns within the 10 columns that follow it. To pad before the $ instead, build the string first and align the whole thing:{"$" + f"{revenue:,.2f}":>11}.
Example 4: Building a Text Table
The same tools scale up to a table: measure each column, then useljust() to pad every cell to that width.
# Create a table
def create_table(headers, rows):
"""Create a simple text table."""
# Calculate column widths
col_widths = [len(h) for h in headers]
for row in rows:
for i, cell in enumerate(row):
col_widths[i] = max(col_widths[i], len(str(cell)))
# Create separator
separator = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
# Header row
header_row = "| " + " | ".join(
h.ljust(col_widths[i]) for i, h in enumerate(headers)
) + " |"
# Data rows
data_rows = []
for row in rows:
data_row = "| " + " | ".join(
str(cell).ljust(col_widths[i]) for i, cell in enumerate(row)
) + " |"
data_rows.append(data_row)
# Combine all parts
result = [separator, header_row, separator]
result.extend(data_rows)
result.append(separator)
return "\n".join(result)
# Use the table function
headers = ["Name", "Score", "Grade"]
rows = [
["Alice", "95", "A"],
["Bob", "87", "B"],
["Charlie", "92", "A"],
]
print(create_table(headers, rows))Expected Output:
+---------+-------+-------+ | Name | Score | Grade | +---------+-------+-------+ | Alice | 95 | A | | Bob | 87 | B | | Charlie | 92 | A | +---------+-------+-------+
Example 5: Password Validation
Validate password strength using string methods.
def validate_password(password):
"""
Validate password meets security requirements:
- At least 8 characters
- Contains uppercase and lowercase
- Contains at least one digit
- Contains at least one special character
"""
errors = []
# Check length
if len(password) < 8:
errors.append("Password must be at least 8 characters")
# Check for uppercase
if not any(c.isupper() for c in password):
errors.append("Password must contain at least one uppercase letter")
# Check for lowercase
if not any(c.islower() for c in password):
errors.append("Password must contain at least one lowercase letter")
# Check for digit
if not any(c.isdigit() for c in password):
errors.append("Password must contain at least one digit")
# Check for special character
special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?"
if not any(c in special_chars for c in password):
errors.append("Password must contain at least one special character")
return errors
# Test the validator
password = "MyPass123!"
errors = validate_password(password)
if errors:
print("Password validation failed:")
for error in errors:
print(f" - {error}")
else:
print("Password is strong!")
# Test various passwords
test_passwords = [
"weak", # Too short, no uppercase, no digit, no special
"WeakPassword", # No digit, no special
"WeakPass123", # No special
"Strong@Pass1", # Valid!
]
for pwd in test_passwords:
errors = validate_password(pwd)
status = "✓ Valid" if not errors else "✗ Invalid"
print(f"{pwd:<20} {status}")Expected Output:
Password is strong! weak ✗ Invalid WeakPassword ✗ Invalid WeakPass123 ✗ Invalid Strong@Pass1 ✓ Valid
Key Takeaways
String Basics
- Strings are immutable sequences
- Use
[index]to access characters - Use
[start:end:step]for slicing - Negative indices count from the end
[::-1]reverses a string
Formatting
- Use f-strings for modern formatting
- Format:
f"{variable:.2f}" .format()for templates- Avoid
%formatting in new code - Use format specifiers for alignment and padding
Essential Methods
.upper(),.lower()for case.strip()removes whitespace.split()and.join()for lists.replace()for modifications.find(),.startswith(),.endswith()
Best Practices
- Clean user input with
.strip() - Use
.lower()for case-insensitive comparison - Validate with
.isdigit(),.isalpha() - Methods return new strings (immutable)
- Chain methods for complex transformations
What's Next?
You've mastered string manipulation, a fundamental skill for any Python developer! In the next lessons, we'll explore:
- List comprehensions & lambda functions - Write cleaner, more Pythonic code with elegant one-liners
- Standard library modules - Discover powerful built-in tools for dates, files, and more
- Debugging basics - Learn to find and fix bugs like a professional developer