Python技术迷

Python 中 read、readline、readlines 有哪些区别?

日志文件一大,readlines() 这东西就开始露馅。

本地跑着没事,线上一拉 2GB 的 access.log,进程内存直接往上顶。代码看着还挺无辜:

with open("access.log", "r", encoding="utf-8") as f:
    lines = f.readlines()

for line in lines:
if"ERROR"in line:
        print(line)

这段我一般第一眼就不太信。

不是因为它语法有问题,而是它把整个文件一次性塞进了内存。小文件无所谓,配置文件、几十行 CSV、测试数据都能这么干。日志、导出文件、用户上传的大文本,就别这么写了。

Python 里读文件常见就这三个:read()、readline()、readlines()。

名字长得像,脾气完全不一样。

read() 是一次读一坨。

with open("order_2026_05.log", "r", encoding="utf-8") as f:
    content = f.read()

print(content[:200])

不传参数,它会把文件剩余内容全部读出来,返回一个字符串。

这个适合读小文件,比如一份模板、一段 SQL、一份 JSON 配置。

with open("sql/check_user.sql", "r", encoding="utf-8") as f:
    sql = f.read()

sql = sql.replace("${day}", "2026-05-10")
print(sql)

但 read() 不是只能全读,也可以指定大小。

with open("upload_packet.bin", "rb") as f:
    head = f.read(16)
    body = f.read(1024)

print(head.hex())
print(len(body))

这里要注意,文本模式下 read(10) 读的是 10 个字符,二进制模式下 read(10) 读的是 10 个字节。排查文件头、协议包、图片头这些东西,我一般都用 rb,少一点编码干扰。

readline() 是一次读一行。

with open("access.log", "r", encoding="utf-8") as f:
    first = f.readline()
    second = f.readline()

print(first.rstrip())
print(second.rstrip())

它每次只往后挪一行,返回的字符串通常带着结尾的 \n。所以很多代码会写 rstrip(),不然打印时容易多空一行。

readline() 的好处是稳。文件再大,它也不是一下子吞进去。

比如线上查某个订单号,我更愿意这样写:

deffind_order_log(path, order_no):
with open(path, "r", encoding="utf-8", errors="replace") as f:
        line_no = 0
whileTrue:
            line = f.readline()
if line == "":
break

            line_no += 1
if order_no in line:
return line_no, line.rstrip()

returnNone, None

line_no, text = find_order_log("pay.log", "ORDER_930021")
if text:
    print(f"hit line={line_no}, log={text}")
else:
    print("not found")

这里有个小细节:判断结束用 line == "",不要直接写 if not line 来糊弄。

空行读出来是 "\n",不是空字符串。文件真的读到末尾,才是 ""。

当然,平时更常见的写法不是手写 readline(),而是直接迭代文件对象:

with open("pay.log", "r", encoding="utf-8", errors="replace") as f:
for line_no, line in enumerate(f, start=1):
if"timeout"in line or"Traceback"in line:
            print(line_no, line.rstrip())

这个底层也是按流式方式往后读,内存压力小,代码还干净。处理大日志,我基本优先写这种。

readlines() 是一次把所有行读成列表。

with open("users.txt", "r", encoding="utf-8") as f:
    rows = f.readlines()

print(type(rows))
print(rows[:3])

返回结果大概这样:

[
"1001,alice\n",
"1002,bob\n",
"1003,cindy\n",
]

它不是返回一个大字符串,而是返回一个列表,列表里每个元素是一行。

这东西适合小文件,而且适合你确实需要“所有行一起处理”的场景。比如读取一份白名单,再做清洗:

defload_user_whitelist(path):
with open(path, "r", encoding="utf-8") as f:
        rows = f.readlines()

    users = set()
for row in rows:
        row = row.strip()
ifnot row or row.startswith("#"):
continue
        users.add(row)

return users

white_users = load_user_whitelist("white_users.conf")
print("U10086"in white_users)

这种配置文件就几百行,readlines() 没啥毛病。别把它拿去读大文件就行。

我见过一个比较典型的慢代码,导入供应商给的商品文件,几十万行,代码这么写:

with open("goods.csv", "r", encoding="utf-8") as f:
    rows = f.readlines()

for row in rows:
    save_goods(row)

这段不只是内存难看,还有一个问题:前面读完整个文件,后面才开始处理。文件越大,等待越明显。

我一般会改成边读边处理:

defimport_goods(path):
    ok_count = 0
    bad_count = 0

with open(path, "r", encoding="utf-8", errors="replace") as f:
for line_no, line in enumerate(f, start=1):
            line = line.strip()
ifnot line:
continue

            parts = line.split(",")
if len(parts) < 4:
                bad_count += 1
                print(f"bad row line={line_no}, data={line}")
continue

            sku, name, price, stock = parts[:4]
try:
                price = int(price)
                stock = int(stock)
except ValueError:
                bad_count += 1
                print(f"bad number line={line_no}, data={line}")
continue

# 这里换成真实入库逻辑
# goods_repo.upsert(sku, name, price, stock)
            ok_count += 1

return ok_count, bad_count

ok, bad = import_goods("goods.csv")
print(f"import done, ok={ok}, bad={bad}")

这段代码不花哨,但线上更扛事。哪一行坏了能打出来,文件再大也不会先吃一大口内存。

还有个容易误会的点:read()、readline()、readlines() 都会受“文件指针”影响。

with open("demo.txt", "r", encoding="utf-8") as f:
    print(f.readline())
    print(f.read())

第一行被 readline() 读走了,后面的 read() 只能读剩下的内容。

想重新读,要么重新打开文件,要么 seek(0):

with open("demo.txt", "r", encoding="utf-8") as f:
    first = f.readline()

    f.seek(0)
    all_text = f.read()

print(first.rstrip())
print(all_text[:100])

这几个方法的选择,我通常就按文件大小和处理方式来判断。

读小配置、小模板、小 JSON:read()。

只看第一行,或者手动控制一行一行读:readline()。

小文件,并且后面确实要拿到所有行列表:readlines()。

大日志、大 CSV、大导入文件:不要上来就 readlines(),直接 for line in f。

很多 Python 文件处理问题,不是语法问题,是读法太贪。

小文件怎么写都行,大文件就别赌内存。文件越大,代码越应该一口一口吃。