The str.index(sub[, start[, end]]) method searches for a substring within a string and returns the lowest 0-based index where the substring starts. If the substring is not found, it raises a ValueError: substring not found exception.
The Key Contrast with find(): While str.find() returns -1 when a substring is missing, index() enforces strict sequence semantics identical to list.index(). Use index() when the target substring is mandatory and its absence indicates corrupted data or a protocol error.
The index() method accepts the search target and optional slice offsets:
position = string.index(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. |
ValueError |
Exception | Raised when sub is not found within the specified range. |
Because missing substrings trigger a ValueError, production scripts wrap index() in a try...except block when input certainty is not guaranteed:
message = "Welcome to plus2net.com"
# 1. Successful lookup returns 0-based offset
print("Index of 'Welcome':", message.index("Welcome")) # 0
print("Index of 'plus': ", message.index("plus")) # 11
print("Index of 'co': ", message.index("co")) # 3 ('co' in 'Welcome')
# 2. Substring absent raises ValueError
try:
pos = message.index("Python")
except ValueError as e:
print("Error caught:", e)
Output:
Index of 'Welcome': 0
Index of 'plus': 11
Index of 'co': 3
Error caught: substring not found
The start and end parameters restrict the search to a specific slice window without copying string data:
phrase = "alpha beta gamma delta alpha beta"
# Find first occurrence from index 0
print("First 'alpha': ", phrase.index("alpha")) # 0
# Find second occurrence starting after index 5
print("Second 'alpha':", phrase.index("alpha", 5)) # 23
# Search strictly between index 5 and 20
print("Beta in [5:20]:", phrase.index("beta", 5, 20)) # 6
# Searching an interval where substring is absent raises ValueError
try:
phrase.index("alpha", 5, 20)
except ValueError:
print("'alpha' not found between 5 and 20")
Output:
First 'alpha': 0
Second 'alpha': 23
Beta in [5:20]: 6
'alpha' not found between 5 and 20
New Python developers often ask why Python provides two nearly identical methods. The explanation lies in sequence consistency:
list and tuple) have an .index() method that raises ValueError on missing items. They do not have a find() method because in Python lists, -1 is a valid index representing the last element. If list.find(x) returned -1 on failure, my_list[my_list.find(x)] would silently corrupt data by returning the last item.find() returning -1 specifically for strings. str.index() was retained so strings fully satisfy Python's standard Sequence API.While str.index() searches left-to-right for the lowest index, str.rindex() searches right-to-left for the highest index, raising ValueError if missing:
filepath = "project/models/user/profile.py"
# First slash (directory root separator)
first_sep = filepath.index("/")
print("First separator:", first_sep) # 7
# Last slash (basename separator)
last_sep = filepath.rindex("/")
print("Last separator: ", last_sep) # 19
print("Filename only: ", filepath[last_sep + 1:]) # 'profile.py'
Output:
First separator: 7
Last separator: 19
Filename only: profile.py
The table below summarizes the key operational differences between substring search techniques:
| Tool | If Match Found | If Not Found | Exception Raised? | Works on Lists/Tuples? |
|---|---|---|---|---|
str.index() |
Lowest Index (0+) | ValueError | Yes | Yes |
str.find() |
Lowest Index (0+) | -1 | No | No (strings only) |
in operator |
True |
False |
No | Yes |
str.rindex() |
Highest Index | ValueError | Yes | No (strings only) |
When parsing configuration lines (e.g. "DATABASE_URL=postgres://localhost"), a missing = delimiter is a syntax error that should fail immediately:
def parse_config_line(line):
line = line.strip()
if not line or line.startswith("#"):
return None
try:
sep = line.index("=")
key = line[:sep].strip()
val = line[sep + 1:].strip()
return key, val
except ValueError:
raise ValueError(f"Malformed config line missing '=': '{line}'")
print("Parsed:", parse_config_line("HOST = 127.0.0.1"))
Output:
Parsed: ('HOST', '127.0.0.1')
HTTP/1.1 headers strictly require a colon followed by space (": "):
def parse_http_header(header_line):
try:
delim = header_line.index(": ")
header_name = header_line[:delim]
header_value = header_line[delim + 2:]
return header_name, header_value
except ValueError:
return None
print("Valid header: ", parse_http_header("Content-Type: text/html"))
print("Invalid header:", parse_http_header("MalformedHeaderWithoutColon"))
Output:
Valid header: ('Content-Type', 'text/html')
Invalid header: None
The following production script implements a strict record parser, safe slice searcher, and delimiter extractor with automated self-checking unit assertions:
class StrictRecordParser:
@staticmethod
def extract_strict_key_value(raw_line):
# Parses key=value lines, raising ValueError if delimiter is absent
if not isinstance(raw_line, str):
raise TypeError("Input must be a string.")
line = raw_line.strip()
eq_idx = line.index("=")
key = line[:eq_idx].strip()
val = line[eq_idx + 1:].strip()
if not key:
raise ValueError("Key cannot be empty.")
return key, val
@staticmethod
def safe_has_delimiter(text, delim):
# Gracefully checks if delimiter can be indexed without raising error
try:
text.index(delim)
return True
except ValueError:
return False
# --- Unit Assertions and Verification ---
parser = StrictRecordParser()
# 1. Valid record extraction
k1, v1 = parser.extract_strict_key_value("DEBUG = True")
assert k1 == "DEBUG"
assert v1 == "True"
k2, v2 = parser.extract_strict_key_value("DB_PORT=5432")
assert k2 == "DB_PORT"
assert v2 == "5432"
# 2. ValueError verification on missing delimiter
try:
parser.extract_strict_key_value("MALFORMED_LINE")
assert False, "Expected ValueError was not raised"
except ValueError as e:
assert "substring not found" in str(e)
# 3. Safe delimiter verification
assert parser.safe_has_delimiter("USER=admin", "=") is True
assert parser.safe_has_delimiter("USER:admin", "=") is False
print("Parsed valid record: ", parser.extract_strict_key_value("TIMEOUT=30"))
print("Delimiter present check:", parser.safe_has_delimiter("KEY=VAL", "="))
print("All str.index() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all substring index search examples interactively, test ValueError handling, benchmark index() vs. find(), and parse configuration lines in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.