Locating Substring Offsets in Python: Safe Searching with find()

The str.find(sub[, start[, end]]) method searches for a substring within a string and returns the lowest 0-based index where the substring begins. If the substring is not present, it returns -1 rather than raising an exception.

find() vs. index() & the "in" Operator: Use the in operator for simple boolean membership checks ("cat" in text). Use find() when you need the integer character offset without wrapping calls in try...except ValueError blocks required by str.index().

Method Signature & Parameters 🔝

The find() method accepts the search target and optional slice offsets:

index_pos = string.find(sub, start=0, end=len(string))
Parameter Type Description
sub str Required. The substring to search for.
start int Optional (default=0). Starting index (inclusive) of slice. Negative values count from end.
end int Optional (default=len). Ending index (exclusive) of slice. Negative values count from end.
Return Value int Returns lowest 0-based index where sub starts; returns -1 if not found.

Basic Usage & Character Positioning 🔝

Indices in Python are 0-based. The returned index points to the first character of the matching substring:

phrase = "Welcome to plus2net.com"

# 1. Substring present at beginning/middle
print("Index of 'Welcome':", phrase.find("Welcome"))  # 0
print("Index of 'plus':   ", phrase.find("plus"))     # 11
print("Index of 'co':     ", phrase.find("co"))       # 3 ('co' in 'Welcome')

# 2. Substring not found returns -1
print("Index of 'Python': ", phrase.find("Python"))   # -1

# 3. Case sensitivity
print("Index of 'PLUS':   ", phrase.find("PLUS"))     # -1 (case mismatch)

Output:

Index of 'Welcome': 0
Index of 'plus':    11
Index of 'co':      3
Index of 'Python':  -1
Index of 'PLUS':    -1

Slice Windows: start and end Range Parameters 🔝

Using start and end restricts searching to a substring slice string[start:end] without allocating a new string in memory:

data = "apple pie, apple cider, apple crisp"

# Search whole string: returns first occurrence at index 0
print("First 'apple':     ", data.find("apple"))        # 0

# Start search after index 5: finds second occurrence
print("Second 'apple':    ", data.find("apple", 5))     # 11

# Restrict search between index 5 and 15:
print("Apple in [5:15]:   ", data.find("apple", 5, 15)) # -1 ('apple' ends at index 16)
print("Apple in [5:16]:   ", data.find("apple", 5, 16)) # 11

# Negative slice offsets count from the end
print("Apple in last 15:  ", data.find("apple", -15))   # 24

Output:

First 'apple':      0
Second 'apple':     11
Apple in [5:15]:    -1
Apple in [5:16]:    11
Apple in last 15:   24

find() vs. rfind(): Left-to-Right vs. Right-to-Left Search 🔝

While find() returns the first (lowest index) match from the left, str.rfind() searches from right to left, returning the last (highest index) occurrence:

filepath = "/var/log/nginx/access.2026.log"

first_slash = filepath.find("/")
last_slash = filepath.rfind("/")
extension_dot = filepath.rfind(".")

print("First slash offset:   ", first_slash)    # 0
print("Last slash offset:    ", last_slash)     # 14
print("File extension offset:", extension_dot)   # 26
print("File extension:       ", filepath[extension_dot:])  # '.log'

Output:

First slash offset:    0
Last slash offset:     14
File extension offset: 26
File extension:        .log

find() vs. index() vs. "in" Operator 🔝

Choosing between Python's substring search tools depends on your architectural requirements:

Tool If Found If Not Found Best Used For
str.find() Lowest Index (0+) -1 When you need index position and absence is expected.
str.index() Lowest Index (0+) Raises ValueError When substring presence is mandatory.
in operator True False When you only need a boolean existence check.
str.rfind() Highest Index (from right) -1 Finding the last occurrence of a delimiter (e.g. extension dot).

Case-Insensitive Searching with casefold() 🔝

Because find() is strictly case-sensitive, pair it with str.casefold() for international case-insensitive lookups:

def find_caseless(haystack, needle, start=0, end=None):
    if end is None:
        end = len(haystack)
    return haystack.casefold().find(needle.casefold(), start, end)

text = "Learn Python Programming at Plus2Net"
print("Search 'python':", find_caseless(text, "python"))   # 6
print("Search 'PYTHON':", find_caseless(text, "PYTHON"))   # 6

Output:

Search 'python': 6
Search 'PYTHON': 6

Looping to Find All Substring Occurrences 🔝

While find() only returns the first matching offset, an idiomatic while loop advances the start parameter to collect all occurrences:

def find_all_occurrences(text, sub):
    positions = []
    start = 0
    while True:
        pos = text.find(sub, start)
        if pos == -1:
            break
        positions.append(pos)
        start = pos + len(sub)  # Advance past match
    return positions

sample = "banana bandana band"
print("All 'an' positions:", find_all_occurrences(sample, "an"))

Output:

All 'an' positions: [1, 3, 8, 10, 16]

Real-World Applications: Email Parsing & Tag Extraction 🔝

1. Extracting Content Between Delimiter Tags

In HTML and template parsing, extracting text enclosed by opening and closing tags uses find() to avoid regex engine overhead:

def extract_tag_content(html, open_tag, close_tag):
    start_idx = html.find(open_tag)
    if start_idx == -1:
        return None
    content_start = start_idx + len(open_tag)
    end_idx = html.find(close_tag, content_start)
    if end_idx == -1:
        return None
    return html[content_start:end_idx]

snippet = "<title>Python str.find() Guide</title>"
print("Extracted title:", extract_tag_content(snippet, "<title>", "</title>"))

Output:

Extracted title: Python str.find() Guide

2. Parsing Email Usernames & Domains

Splitting email addresses into local-part and domain components using the '@' index offset:

def parse_email_components(email):
    at_pos = email.find("@")
    if at_pos == -1 or at_pos == 0 or at_pos == len(email) - 1:
        return None
    return email[:at_pos], email[at_pos + 1:]

print("Valid email:  ", parse_email_components("admin@plus2net.com"))
print("Invalid email:", parse_email_components("no_at_sign.com"))

Output:

Valid email:   ('admin', 'plus2net.com')
Invalid email: None

Hands-on Exercise: Tag Extractor & Offset Finder with Assertions 🔝

The following production script implements an offset finder, tag extractor, and safe sub-slice searcher with automated unit test assertions:

Python Exercise: Substring Offset & Tag Extractor Suite
class StringSearchEngine:
    @staticmethod
    def find_all(text, sub):
        # Finds all non-overlapping occurrences of sub in text
        if not sub:
            return []
        indices = []
        start = 0
        while True:
            pos = text.find(sub, start)
            if pos == -1:
                break
            indices.append(pos)
            start = pos + len(sub)
        return indices

    @staticmethod
    def extract_between(text, start_delim, end_delim):
        # Extracts text strictly between two boundary delimiters
        p1 = text.find(start_delim)
        if p1 == -1:
            return None
        content_start = p1 + len(start_delim)
        p2 = text.find(end_delim, content_start)
        if p2 == -1:
            return None
        return text[content_start:p2]

# --- Unit Assertions and Verification ---
engine = StringSearchEngine()

# 1. Basic find checks
text = "The quick brown fox jumps over the lazy dog"
assert text.find("quick") == 4
assert text.find("cat") == -1
assert text.find("The", 1) == -1  # 'The' is at 0, searching from 1 returns -1
assert text.find("the") == 31    # Lowercase 'the'

# 2. Find all occurrences
seq = "abc-abc-abc-abc"
assert engine.find_all(seq, "abc") == [0, 4, 8, 12]
assert engine.find_all(seq, "xyz") == []
assert engine.find_all(seq, "") == []

# 3. Delimited text extraction
html = "<header>Welcome to Python</header><p>Body content</p>"
assert engine.extract_between(html, "<header>", "</header>") == "Welcome to Python"
assert engine.extract_between(html, "<p>", "</p>") == "Body content"
assert engine.extract_between(html, "<footer>", "</footer>") is None
assert engine.extract_between(html, "<header>", "</footer>") is None

print("Extracted header:", engine.extract_between(html, "<header>", "</header>"))
print("All 'abc' offsets:", engine.find_all(seq, "abc"))
print("All str.find() unit assertions passed successfully!")

Practice in Google Colab & GitHub 🔝

Open in Google Colab View on GitHub

Run all substring search examples interactively, evaluate start/end slice windows, benchmark find() vs. index(), and extract delimited tags in Google Colab.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer