The file.seek(offset, whence=os.SEEK_SET) method moves the file stream pointer to an arbitrary byte position, enabling non-sequential random read and write access across files.
Text vs. Binary Rules: In binary mode ('rb', 'r+b'), Python supports arbitrary forward and backward seeks from the start, current cursor, or end of file. In text mode ('r'), relative seeks are strictly forbidden by CPython due to multi-byte encoding boundaries.
The standard CPython signature for file.seek() is:
file_object.seek(offset, whence=0)
| Parameter | Type | Default | Behavior & Description |
|---|---|---|---|
offset |
int |
Required | Number of bytes to move the cursor. Can be positive (forward) or negative (backward, in binary mode). |
whence |
int |
0 (os.SEEK_SET) |
Reference point for the movement: 0 = file start, 1 = current pointer, 2 = file end. |
| Return Value | int |
— | The new absolute byte offset from the start of the file. |
Python provides symbolic constants in the standard os module that enhance code readability when specifying the reference point:
| Integer | os Constant | Reference Anchor | Permitted Offset Values |
|---|---|---|---|
0 |
os.SEEK_SET |
Beginning of the file (offset 0) | ≥ 0 (positive integers or 0) |
1 |
os.SEEK_CUR |
Current file pointer location | Positive (forward) or negative (backward) in binary mode |
2 |
os.SEEK_END |
End of the file (EOF) | ≤ 0 (negative integers to seek backward from EOF) |
Because modern text encodings like UTF-8 use variable-length byte representations (1 to 4 bytes per character), jumping to an arbitrary byte offset in text mode can split a multi-byte Unicode sequence in half. Therefore, Python 3 enforces strict rules for text files ('r', 'w', 'a'):
seek(0) — Rewinding to the very beginning of the file.seek(0, os.SEEK_END) — Jumping to the exact end of the file.seek(saved_offset) — Passing an opaque integer returned earlier by tell().seek(5, 1) or seek(-10, 2)) — Raises io.UnsupportedOperation: can't do nonzero cur-relative seeks.import io
with open("document.txt", "r", encoding="utf-8") as f:
# Valid: Rewind to start
f.seek(0)
# Valid: Jump to end of file
f.seek(0, 2)
# Invalid in text mode: Raises UnsupportedOperation
try:
f.seek(5, 1)
except io.UnsupportedOperation as err:
print("Relative seek forbidden in text mode:", err)
In binary mode, Python treats files as contiguous arrays of raw bytes without encoding translation. All three whence anchor points and positive/negative offsets are fully supported:
import os
with open("payload.bin", "rb") as fob:
# 1. SEEK_SET: Jump 10 bytes forward from start
fob.seek(10, os.SEEK_SET)
print("Byte at offset 10:", fob.read(4))
# 2. SEEK_CUR: Skip 6 bytes forward from current location
fob.seek(6, os.SEEK_CUR)
print("Byte after relative skip:", fob.read(4))
# 3. SEEK_END: Read last 8 bytes of the file
fob.seek(-8, os.SEEK_END)
print("Final 8 bytes of file:", fob.read(8))
The most common production use case for seek() is resetting the read cursor after parsing, writing, or benchmarking, avoiding costly file re-opening:
with open("dataset.csv", "r", encoding="utf-8") as stream:
# Pass 1: Count total rows
row_count = sum(1 for _ in stream)
print(f"Total records found: {row_count}")
# Rewind cursor back to the start
stream.seek(0)
# Pass 2: Ingest header definition
header = stream.readline().strip()
print(f"Header columns: {header}")
Inspect the trailer or recent log entries from a multi-gigabyte file in milliseconds by seeking backward from EOF:
import os
def read_file_tail(filepath: str, num_bytes: int = 1024) -> bytes:
file_size = os.path.getsize(filepath)
seek_offset = min(num_bytes, file_size)
with open(filepath, "rb") as f:
f.seek(-seek_offset, os.SEEK_END)
return f.read(seek_offset)
recent_bytes = read_file_tail("server.log", 256)
print("Tail log buffer loaded instantly without reading whole file.")
Update specific fields in fixed-width binary databases or header files without rewriting the entire dataset:
def patch_header_flag(filepath: str, byte_offset: int, new_value: bytes) -> None:
with open(filepath, "r+b") as bin_stream:
bin_stream.seek(byte_offset, 0)
bin_stream.write(new_value)
patch_header_flag("firmware.bin", 4, b"\x01")
print("Header flag patched in-place at byte offset 4.")
Under POSIX and C runtime specifications, files opened in append mode ('a' or 'a+') forcibly redirect all write operations to the end of the file (EOF) regardless of previous seek() invocations. To modify files in-place, use mode 'r+' or 'r+b'.
Seeking past the end of a file and writing creates a sparse file filled with zero bytes (\x00) between the old EOF and the new write offset. Verify file boundaries before arbitrary seeks if null padding is undesirable.
Run this complete test harness verifying binary seeks, relative cursor jumps, and tail offset reads:
import tempfile
import os
class SeekVerifier:
@staticmethod
def read_span(path: str, offset: int, length: int) -> bytes:
with open(path, "rb") as stream:
stream.seek(offset, os.SEEK_SET)
return stream.read(length)
# Verification unit assertions
with tempfile.TemporaryDirectory() as td:
v = SeekVerifier()
test_file = os.path.join(td, "verify.bin")
raw_payload = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
with open(test_file, "wb") as f:
f.write(raw_payload)
# Test 1: Absolute seek from start (SEEK_SET)
assert v.read_span(test_file, 10, 5) == b"ABCDE"
# Test 2: Seek from end (SEEK_END)
with open(test_file, "rb") as f:
f.seek(-4, os.SEEK_END)
assert f.read(4) == b"WXYZ"
# Test 3: Relative seek (SEEK_CUR)
with open(test_file, "rb") as f:
f.seek(10)
f.seek(5, os.SEEK_CUR)
assert f.read(5) == b"FGHIJ"
print("All file.seek() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all file seek exercises interactively, test SEEK_SET, SEEK_CUR, and SEEK_END whence anchors, read log tails, and patch binary headers 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.