bytes.index()
Identical to find except for the failure mode. Note the error message says SUBSECTION, not substring — a small tell that you are in the bytes world.
Demo
Same scan as find, different ending. A match gives its byte offset; no match raises ValueError rather than handing back -1. The multi-byte case shows the offset is measured in BYTES — l sits at 3 in the encoded form of "héllo" even though it is the third character. Choose this over find when a missing marker means the input is malformed.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| sub | bytes | int | yes | Byte sequence to locate. An int from 0 to 255 searches for that single byte. |
| start | int | no (0) | Byte offset to begin at. The result stays absolute. |
| end | int | no (len) | Byte offset to stop at, exclusive. |
Return value
int — Byte offset of the first occurrence. Raises ValueError with the message "subsection not found" when absent.
Common patterns
i = payload.index(b'\r\n\r\n') header, body = payload[:i], payload[i+4:]
try: i = data.index(b'MAGIC') except ValueError: raise ParseError('missing magic bytes')
i = 0 while True: try: i = data.index(sub, i) + 1 except ValueError: break
Examples
Pitfalls
b'abc'.index(b'z')
i = b'abc'.find(b'z') if i == -1: ...
except ValueError as e: assert 'substring' in str(e)
except ValueError: handle()
try: b'abc'.index('z') except ValueError: ...
b'abc'.index('z'.encode())
When to use
- A delimiter that must be present in a well-formed payload
- Parsing where a missing marker means malformed input
- You would write "if i == -1: raise" anyway
- Absence is normal → find, which returns -1
- You only need presence → the in operator
- Searching from the right → rindex
Notes
FAQ
Because a bytes object holds no strings — "substring" would imply text. It is a small wording change, but enough to break tests that assert on the message when data switches from str to bytes.
b'abc'.index(b'z') # ValueError: subsection not found