HOWTO · Python
使用 Python 讀取檔案的最後一行
我們可以使用 for 迴圈和 file.readlines() 函式在 Python 中讀取檔案的最後一行。
本頁內容
在 Python 中,逐行讀取檔案並保留最新讀到的一行,就能取得最後一行。這種方式會從頭讀到檔案末端,但一次只儲存一行。若檔案非常大,完整掃描太慢,可以改用二進位區塊從檔案末端向前讀取。
執行以下範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "sample.txt"
path.write_text("alpha\nbeta\ngamma\n", encoding="utf-8")
last_line = None
with path.open("r", encoding="utf-8") as file:
for line in file:
last_line = line.rstrip("\r\n")
print(last_line)
gamma
在 Python 中讀取檔案的最後一行
Python 的檔案物件沒有直接取得「最後一行」的操作。逐行讀取很簡單,額外使用的記憶體也很少,但仍須讀到檔案末端。若檔案大小適合放入記憶體,使用 readlines() 程式碼較簡短;若檔案很大,從末端向前掃描的二進位讀取方式可以避免讀取前面的內容。選擇哪種方法,取決於你最重視簡單、記憶體用量,還是避免完整掃描。
使用 for 迴圈逐行讀取
用一個變數保存最近讀取的那一行。先將它設為 None,即使檔案沒有任何行,程式也會有明確的結果。rstrip("\r\n") 只會移除行尾字元,因此會保留原本內容中的空格。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "sample.txt"
path.write_text("first\nsecond\n", encoding="utf-8")
last_line = None
with path.open("r", encoding="utf-8") as file:
for line in file:
last_line = line.rstrip("\r\n")
print(last_line)
迴圈會讀取每一行,因此檔案越大,所需時間通常越長。額外記憶體只用來保存目前讀取的行和最後的結果。在 Python 的文字檔迭代器中,檔案末尾的一個換行字元不會多產生一行;若最後一行是以兩個換行字元表示的空白行,讀取結果則會是空字串。
小型檔案使用 readlines()
readlines() 會將所有行放進一個串列,因此很容易選取最後一個項目。由於空檔案會產生空串列,索引前請先檢查串列是否有內容。文字模式會將每行的行尾字元保留在回傳字串中;若不需要行尾字元,只移除 \r 和 \n 即可。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "sample.txt"
path.write_text("first\nsecond\n", encoding="utf-8")
with path.open("r", encoding="utf-8") as file:
lines = file.readlines()
last_line = lines[-1].rstrip("\r\n") if lines else None
print(last_line)
這種方式同樣會掃描整個檔案,並將每一行都保留在記憶體中。若檔案不大,或其他地方也會用到這個串列,使用它很方便;若開頭或結尾的空格具有意義,請勿使用 strip()。
使用 deque 只保留最後一行
設定 maxlen=1 的 deque 會在每讀入一行時丟棄較早的項目。它和 for 迴圈一樣會依序掃描檔案,且記憶體用量低,但不必明確管理保存結果的變數。呼叫 pop() 前先檢查結果是否有內容,才能妥善處理空檔案。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from collections import deque
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "sample.txt"
path.write_text("first\nsecond\n", encoding="utf-8")
with path.open("r", encoding="utf-8") as file:
lines = deque(file, maxlen=1)
last_line = lines.pop().rstrip("\r\n") if lines else None
print(last_line)
deque 限制的是保留的行數,而非讀取的輸入量:它仍然會讀到檔案末端。若要在串流讀取檔案時只保留最新項目,這種方式很實用。
使用 seek() 從檔案末端向前讀取
處理大型檔案時,以二進位模式開啟檔案,就能安全地使用 seek() 移動到位元組位置。下方的函式從檔案末端開始;若末尾有一個換行字元,便先移除,再以固定大小的區塊向前搜尋前一個換行字元。它只會合併屬於最後一行的區塊,因此記憶體用量取決於最後一行的長度,而非整個檔案的大小。此方法支援 LF 與 CRLF 行尾,並會依指定的編碼解碼選出的位元組。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
from pathlib import Path
from tempfile import TemporaryDirectory
def read_last_line(path, encoding="utf-8", chunk_size=8192):
if chunk_size <= 0:
raise ValueError("chunk_size must be positive")
with open(path, "rb") as file:
file.seek(0, 2)
end = file.tell()
if end == 0:
return None
file.seek(end - 1)
position = end - 1 if file.read(1) == b"\n" else end
chunks = []
while position > 0:
start = max(0, position - chunk_size)
file.seek(start)
chunk = file.read(position - start)
newline = chunk.rfind(b"\n")
if newline >= 0:
chunks.append(chunk[newline + 1:])
line = b"".join(reversed(chunks))
return (line[:-1] if line.endswith(b"\r") else line).decode(encoding)
chunks.append(chunk)
position = start
line = b"".join(reversed(chunks))
return (line[:-1] if line.endswith(b"\r") else line).decode(encoding)
with TemporaryDirectory() as directory:
path = Path(directory) / "sample.txt"
path.write_bytes("first\r\nlast line\r\n".encode("utf-8"))
print(read_last_line(path))
若一行比單一區塊長,函式會持續累積資料,直到找到分隔符號;若整個檔案只有一行,則會一路讀回檔案開頭。選出的那一行必須能以指定編碼解碼。此實作將 LF 和 CRLF 視為分隔符號,不支援以單獨 CR 作為行分隔符號的檔案。使用此函式時,chunk_size 必須大於 0;設為 0 時,向前搜尋可能無法前進。若遇到特殊編碼或換行慣例,請改用文字串流讀取器,或調整分隔符號的處理方式。
在類 Unix 系統上使用 tail
若系統有提供外部 tail 指令,就能直接取得最後一行,不必自行實作向後掃描。從 Python 呼叫時,可用 subprocess.run(["tail", "-n", "1", path], capture_output=True, check=True) 擷取位元組,再依檔案使用的編碼解碼 result.stdout。此方法需要系統已安裝 tail,而且該指令位於 PATH 中;它不是 Windows 原生解決方案。請傳入引數串列,不要將路徑組成 shell 指令。指令的行為和速度會因作業系統與檔案而異。
選擇方法並處理邊界情況
| 方法 | 會讀取整個檔案嗎? | 額外記憶體 | 適用情況 |
|---|---|---|---|
for 迴圈 |
會 | 一行 | 需要直接易懂的串流讀取程式碼。 |
readlines() |
會 | 所有行 | 檔案較小,或之後還需要使用整個串列。 |
deque 搭配 maxlen=1 |
會 | 一行 | 想以精簡方式保存串流讀取狀態。 |
使用 seek() 從末端反向讀取二進位區塊 |
通常不會;若最後一行很長,可能讀到檔案開頭 | 最後一行加上一個區塊 | 檔案很大,且使用 LF 或 CRLF。 |
tail |
取決於實作方式 | 外部程序 | 目標系統有提供此指令。 |
若檔案為空,請回傳 None 或其他有明確說明的預設值,不要未經檢查就索引或呼叫 pop()。先決定結果是否要包含行尾字元;若不需要,只移除 \r 和 \n。for 迴圈和 deque 用量少,但仍會掃描整個檔案。對小型檔案而言,readlines() 是最簡單的串列讀取方式。若避免讀取前面的內容很重要,而且檔案符合其編碼與換行假設,可使用反向 seek() 方法。若可接受使用類 Unix 系統的外部工具,則可選擇 tail。