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 会在每次读入新行时丢弃较早的内容。它和循环一样会按顺序扫描文件,且只占用少量内存,但状态处理更简洁。调用 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 字符作为行分隔符的文件。对于不常见的编码或换行约定,请使用流式文本读取器,或调整分隔符处理方式。
在类 Unix 系统上使用 tail
在提供该命令的系统上,可以使用外部命令 tail 返回文件的最后一行,无须自行实现反向扫描。在 Python 中,通过 subprocess.run(["tail", "-n", "1", path], capture_output=True, check=True) 获取字节,再使用文件所用的编码解码 result.stdout。此方法要求系统已安装 tail,且该命令位于 PATH 中;它不是 Windows 的原生解决方案。请传递参数列表,不要把路径拼接成 shell 命令。该命令的行为和速度取决于操作系统及文件本身。
选择合适的方法并处理边界情况
| 方法 | 是否读取整个文件? | 额外内存 | 适用场景 |
|---|---|---|---|
for 循环 |
是 | 一行 | 需要直观的流式读取代码。 |
readlines() |
是 | 所有行 | 文件较小,或后续需要使用行列表。 |
deque with maxlen=1 |
是 | 一行 | 需要简洁地保存流式读取状态。 |
使用 seek() 反向读取二进制块 |
通常否;最后一行很长时可能读到文件开头 | 最后一行加一个读取块 | 文件较大,且使用 LF 或 CRLF 换行。 |
tail |
取决于具体实现 | 外部进程 | 目标系统提供该命令。 |
对于空文件,应返回 None 或其他已说明的哨兵值,不要未经检查就索引或调用 pop()。请决定返回结果是否应包含行结束符;如果不需要,只移除 \r 和 \n。循环和 deque 的内存用量很少,但仍会扫描整个文件。对于小文件,readlines() 是最简单的列表方案。如果避免读取前面的内容很重要,且文件符合其编码和换行假设,可以使用反向 seek() 方法。若可以接受调用类 Unix 系统上的外部工具,则可选择 tail。