Python - Substring (Slice String)

Python implements substring extraction through slice notation using square brackets. The fundamental syntax is `string[start:stop]`, where `start` is inclusive and `stop` is exclusive.

Key Insights

  • Python string slicing uses bracket notation with start:stop:step syntax, where indices are zero-based and the stop index is exclusive
  • Negative indices count from the end of the string, enabling elegant extraction of suffixes and reverse traversal without calculating string length
  • String slicing creates new string objects rather than modifying originals, with advanced techniques like stride patterns and slice objects providing powerful text manipulation capabilities

Basic String Slicing Syntax

Python implements substring extraction through slice notation using square brackets. The fundamental syntax is string[start:stop], where start is inclusive and stop is exclusive.

text = "Application Architecture"

# Extract first 11 characters
print(text[0:11])  # "Application"

# Extract from index 12 to end
print(text[12:])  # "Architecture"

# Extract middle portion
print(text[4:15])  # "ication Arc"

# Omitting start defaults to 0
print(text[:11])  # "Application"

The zero-based indexing means the first character sits at index 0. When you omit the start index, Python assumes 0. When you omit the stop index, Python slices to the string’s end.

Negative Indices

Negative indices count backward from the string’s end, with -1 representing the last character. This eliminates manual length calculations for suffix extraction.

url = "https://example.com/api/v1/users"

# Get last 5 characters
print(url[-5:])  # "users"

# Get everything except last 6 characters
print(url[:-6])  # "https://example.com/api/v1"

# Extract from negative start to negative stop
print(url[-10:-6])  # "v1/u"

# Get last character
print(url[-1])  # "s"

Combining positive and negative indices provides flexible extraction patterns without calculating offsets:

path = "/home/user/documents/report.pdf"

# Remove first and last characters
print(path[1:-1])  # "home/user/documents/report.pd"

# Get extension (last 4 characters)
print(path[-4:])  # ".pdf"

Step Parameter

The full slicing syntax is string[start:stop:step], where step determines the interval between extracted characters.

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

# Every second character
print(alphabet[::2])  # "ACEGIKMOQSUWY"

# Every third character starting from index 1
print(alphabet[1::3])  # "BEHKNQTWZ"

# Reverse the string
print(alphabet[::-1])  # "ZYXWVUTSRQPONMLKJIHGFEDCBA"

# Reverse substring
print(alphabet[5:15][::-1])  # "ONMLKJIHGF"

Negative step values traverse the string backward:

code = "Python3.11"

# Get every second character in reverse
print(code[::-2])  # "1.nyh"

# Reverse first 6 characters
print(code[5::-1])  # "nohtyP"

Practical Extraction Patterns

Real-world applications often require extracting specific portions based on delimiters or patterns.

# Parse email address
email = "john.doe@company.com"
username = email[:email.index('@')]
domain = email[email.index('@')+1:]
print(f"User: {username}, Domain: {domain}")
# User: john.doe, Domain: company.com

# Extract file components
filepath = "/var/log/application.log"
last_slash = filepath.rfind('/')
directory = filepath[:last_slash]
filename = filepath[last_slash+1:]
print(f"Dir: {directory}, File: {filename}")
# Dir: /var/log, File: application.log

# Parse version string
version = "v2.5.1-beta"
major = version[1:2]
minor = version[3:4]
patch = version[5:6]
print(f"Major: {major}, Minor: {minor}, Patch: {patch}")
# Major: 2, Minor: 5, Patch: 1

Slice Objects

For reusable or complex slicing logic, Python provides the slice() function that creates slice objects.

log_entry = "2024-01-15 14:32:01 ERROR Database connection failed"

# Define reusable slices
date_slice = slice(0, 10)
time_slice = slice(11, 19)
level_slice = slice(20, 25)
message_slice = slice(26, None)

print(log_entry[date_slice])     # "2024-01-15"
print(log_entry[time_slice])     # "14:32:01"
print(log_entry[level_slice])    # "ERROR"
print(log_entry[message_slice])  # "Database connection failed"

# Use in loops
logs = [
    "2024-01-15 14:32:01 ERROR Database connection failed",
    "2024-01-15 14:32:05 WARN  Retry attempt 1",
    "2024-01-15 14:32:10 INFO  Connection restored"
]

for log in logs:
    timestamp = log[date_slice] + " " + log[time_slice]
    level = log[level_slice].strip()
    message = log[message_slice]
    print(f"[{timestamp}] {level}: {message}")

Boundary Handling

Python’s slicing gracefully handles out-of-bounds indices without raising exceptions, making it robust for variable-length strings.

text = "Short"

# Stop index beyond string length
print(text[0:100])  # "Short"

# Start index beyond string length
print(text[100:200])  # ""

# Negative indices beyond bounds
print(text[-100:])  # "Short"

# Practical use: safe truncation
def truncate(s, length):
    return s[:length] + ("..." if len(s) > length else "")

print(truncate("Long description", 10))   # "Long descr..."
print(truncate("Short", 10))              # "Short"

String Manipulation Patterns

Combine slicing with concatenation for string transformations:

# Insert substring
original = "Hello World"
insert_pos = 6
result = original[:insert_pos] + "Beautiful " + original[insert_pos:]
print(result)  # "Hello Beautiful World"

# Replace middle section
text = "The quick brown fox"
text = text[:10] + "red" + text[15:]
print(text)  # "The quick red fox"

# Redact sensitive data
ssn = "123-45-6789"
redacted = "***-**-" + ssn[-4:]
print(redacted)  # "***-**-6789"

# Swap parts
name = "Doe, John"
comma_pos = name.index(',')
swapped = name[comma_pos+2:] + " " + name[:comma_pos]
print(swapped)  # "John Doe"

Performance Considerations

String slicing creates new string objects. For extensive string manipulation, consider alternative approaches:

import timeit

# Slicing creates new strings
def slice_approach(text, n):
    result = []
    for i in range(0, len(text), n):
        result.append(text[i:i+n])
    return result

# List comprehension with slicing
def comprehension_approach(text, n):
    return [text[i:i+n] for i in range(0, len(text), n)]

text = "A" * 10000
n = 100

time1 = timeit.timeit(lambda: slice_approach(text, n), number=1000)
time2 = timeit.timeit(lambda: comprehension_approach(text, n), number=1000)

print(f"Loop: {time1:.4f}s")
print(f"Comprehension: {time2:.4f}s")

For building strings from many pieces, use join() instead of repeated concatenation:

# Inefficient
result = ""
for i in range(1000):
    result += str(i) + ","

# Efficient
result = ",".join(str(i) for i in range(1000))

Working with Unicode

Python 3 strings are Unicode by default. Slicing operates on characters, not bytes:

text = "Hello δΈ–η•Œ"

print(text[6:8])   # "δΈ–η•Œ"
print(len(text))   # 8 (characters, not bytes)

# Emoji handling
message = "Python 🐍 rocks πŸš€"
print(message[7:8])   # "🐍"
print(message[:-2])   # "Python 🐍 rocks "

# Byte-level operations require encoding
encoded = text.encode('utf-8')
print(encoded[6:12])  # b'\xe4\xb8\x96\xe7\x95\x8c'

Common Pitfalls

Be aware of these slicing edge cases:

# Empty slices
text = "Example"
print(text[5:2])   # "" (start > stop returns empty)
print(text[10:20]) # "" (out of bounds returns empty)

# Step of zero raises error
try:
    print(text[::0])
except ValueError as e:
    print(f"Error: {e}")  # Error: slice step cannot be zero

# Modifying slices doesn't work (strings are immutable)
text = "Hello"
# text[0:2] = "Ye"  # TypeError: 'str' object does not support item assignment

# Use string methods or slicing + concatenation instead
text = "Ye" + text[2:]
print(text)  # "Yello"

String slicing in Python provides a powerful, readable syntax for substring extraction. Master these patterns to write cleaner code for parsing, validation, and text processing tasks.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.