bytes.find()
The non-raising search. Returns a BYTE offset, which is not the same as a character position once the data holds anything above ASCII.
Demo
find scans forward and reports where the match begins, or -1 if there is none. The multi-byte case is the one that matters: in "héllo" the letter l looks like character 2, but the accented character occupies TWO utf-8 bytes, so the byte offset is 3. Every offset here counts bytes, which is what you need for slicing the bytes object and what will not match a character position from the decoded text.
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 of sub, or -1 if it is not present. Never raises for a missing value.
Common patterns
i = payload.find(b'\r\n\r\n') header, body = payload[:i], payload[i+4:]
if data.find(b'MAGIC') != -1: ...
i = data.find(sub) while i != -1: handle(i) i = data.find(sub, i + 1)
Examples
Pitfalls
d = b'abc' d[:d.find(b'z')]
i = d.find(b'z') result = d[:i] if i != -1 else d
'héllo'.encode().find(b'l'), 'héllo'.find('l')
'héllo'.find('l') # decode first, then search
b'abc'.find('a')
b'abc'.find('a'.encode())
b'abcabc'.find(b'b', 2)
b'abcabc'.find(b'b', 2) - 2
When to use
- Locating a delimiter or magic marker in a binary payload
- Absence is expected and should not raise
- Slicing a buffer around a known separator
- Absence is an error → index, which raises
- You only need presence → the in operator
- The data is text → decode first and search the string
Notes
FAQ
Because bytes count storage and characters count text. In UTF-8 an accented letter takes two bytes, an emoji four, so byte offsets run ahead of character positions as soon as the data leaves ASCII.
'héllo'.encode().find(b'l') # 3 'héllo'.find('l') # 2