| 模式 | tell() 回傳值含義 | 精確度 | 可否配合 seek() |
'rb'/'wb' (二進位) | 精確位元組偏移量 | 100% | 是,任意位置 |
'r'/'w' (文字) | 經編碼/換行轉換後的「邏輯位置」 | 不保證對應實體位元組 | 僅限 seek(0) 或 seek(tell值) |
基礎範例
with open('demo.txt', 'w', encoding='utf-8') as f:
f.write('Hello') # 寫入 5 字元
print(f.tell()) # 5 (UTF-8 各 1 位元組)
f.write('世界') # 寫入 2 中文字元 (UTF-8 各 3 位元組)
print(f.tell()) # 11 (5 + 2*3)
with open('demo.txt', 'r', encoding='utf-8') as f:
print(f.tell()) # 0
f.read(1) # 讀 'H'
print(f.tell()) # 1
f.read(1) # 讀 'e'
print(f.tell()) # 2
進階實戰應用
1. 大文件斷點續讀:記錄每行起始位置建立索引
class LineIndexer:
'''建立文件行位移索引,支援 O(1) 隨機存取任意行'''
def __init__(self, filepath, encoding='utf-8'):
self.filepath = filepath
self.encoding = encoding
self.line_offsets = [0] # 第 1 行從 0 開始
def build(self):
with open(self.filepath, 'rb') as f: # 必須用二進位模式
while True:
offset = f.tell()
line = f.readline()
if not line:
break
self.line_offsets.append(f.tell()) # 下一行起始位移
return self
def get_line(self, line_no): # 1-based
if not (1 <= line_no < len(self.line_offsets)):
raise IndexError('行號超出範圍')
with open(self.filepath, 'rb') as f:
f.seek(self.line_offsets[line_no - 1])
return f.readline().decode(self.encoding).rstrip('\n')
# 使用
indexer = LineIndexer('huge_log.txt').build()
print(indexer.get_line(1000000)) # 瞬間讀取第 100 萬行
2. 斷點續傳下載:記錄已寫入位元組數
def download_with_resume(url, dest_path, chunk_size=8192):
# 1. 取得遠端檔案大小
head = requests.head(url, allow_redirects=True)
total_size = int(head.headers.get('Content-Length', 0))
# 2. 檢查本地已下載
downloaded = os.path.getsize(dest_path) if os.path.exists(dest_path) else 0
if downloaded >= total_size:
print('已完整下載')
return
# 3. 續傳
headers = {'Range': f'bytes={downloaded}-'}
with requests.get(url, headers=headers, stream=True) as r:
with open(dest_path, 'ab') as f:
f.seek(0, 2) # 確保在文件結尾
for chunk in r.iter_content(chunk_size):
f.write(chunk)
downloaded += len(chunk)
print(f'\r進度: {downloaded}/{total_size} ({downloaded*100//total_size}%)', end='')
print()
3. 流式解析二進位協定:記錄關鍵結構位置
def parse_custom_protocol(filepath):
'''解析自訂二進位協定:Header(12B) + N 個 Chunk(各 4B 長度 + 資料)'''
chunks = []
with open(filepath, 'rb') as f:
# 讀 Header
magic, version, chunk_count = struct.unpack('<4sII', f.read(12))
print(f'Magic: {magic}, Version: {version}, Chunks: {chunk_count}')
for i in range(chunk_count):
chunk_start = f.tell() # 記錄 chunk 起始位置
length, = struct.unpack('
4. 文字模式 tell() 的實際陷阱演示
# 建立測試文件 (含中文、CRLF)
with open('trap.txt', 'w', encoding='utf-8', newline='') as f:
f.write('第一行\r\n')
f.write('第二行\r\n')
# 文字模式讀取
with open('trap.txt', 'r', encoding='utf-8') as f:
print(f'開頭: {f.tell()}') # 0
f.readline() # 讀 '第一行\n' (CRLF→LF)
print(f'第1行後: {f.tell()}') # 9 (邏輯位置: 6字元+1換行+2? 實際不固定)
f.seek(f.tell()) # OK: seek 自身 tell 值
# f.seek(5) # OSError! 文字模式不可任意 seek
常數對照:io 模組
import io
# tell() 回傳值可用於 io.SEEK_SET 等常數語境
f.seek(pos, io.SEEK_SET) # 等同 whence=0
最佳實務總結
- 關鍵位置記錄請用二進位模式:
'rb'/'wb' 保證 tell() 回傳真實位元組偏移,可任意 seek()。
- 文字模式僅用於循序讀寫:不要在文字模式依賴
tell() 做隨機存取邏輯。
- 建立索引時一次性掃描:大文件建立行索引只需掃描一次,後續隨機存取極快。
- 配合 struct 模組:解析二進位結構時,
tell() 記錄每個欄位位置,除錯極方便。
- 斷點續傳核心:
tell() / os.path.getsize() 取得已傳輸位元組,seek(0, 2) 定位文件尾追加。
tell() 雖小,卻是 Python 文件隨機存取能力的基石。善用它建立索引、實現續傳、解析二進位協定,能讓你的 I/O 代碼從「能跑」進化為「高效、可靠、可擴展」。
- 上一篇文章: Python 文件隨機存取:seek() 與 tell() 方法完整教學
- 下一篇文章: Ruby 常用檔案操作方法完整指南:建立、讀寫、刪除、重命名、目錄管理
Copyright ©
程式碼之家 All Rights Reserved