这两年用 Python 处理报表,openpyxl 是绕不开的东西。它的文档写得算全,但有几个地方属于「你以为它该这样,它偏不这样」。下面三个,我都是先写错、再被结果打脸,才记住的。
一、read_only 模式会骗人
处理一张几万行的大表,为了省内存,顺手开了 read_only=True。然后按习惯去取一个单元格:
from openpyxl import load_workbook
wb = load_workbook("报表.xlsx", read_only=True)
ws = wb.active
v = ws["B2"].value # 拿到的不是数字
print(float(v)) # 变成 0,或者直接抛异常
原因很简单:read_only 模式下,单格访问返回的是 ReadOnlyCell 对象本身,不是它的值。这个对象被 float() 一强转,要么变成 0,要么报错。麻烦的地方在于——从输出里看不出异常。数字是 0,你会以为那一格本来就是空的。
我因此误判过一次「改造已经生效」。脚本没报错,跑得干干净净,结果全是错的。
后来固定成两种写法,看场景选:
# 方式一:流式读,行号用 enumerate 的变量,别去访问 row[0].row
for i, row in enumerate(ws.iter_rows(min_row=1, values_only=True), start=1):
...
# 方式二:关掉 read_only,单格访问恢复正常
wb = load_workbook("报表.xlsx", read_only=False)
凡是脚本里出现 isinstance(x, (int, float)) 判断的地方,都该回头看一眼取数方式。这个判断本身没错,错的是它拿到的根本不是值。
二、云盘路径会把文件读死
从同步盘的挂载路径直接打开 xlsx,文件只有几兆,程序却卡在那儿一动不动,没有报错,也没有输出,就是等。
一开始怀疑是文件太大,换了小文件还是卡;怀疑是 openpyxl 版本,换了版本还是卡。真正的原因不在库,在那个路径本身——云盘的虚拟文件系统对随机读的支持很差,而 xlsx 是个 zip 包,读取过程需要反复跳转定位。
解法很土,但有效:先复制到本地临时目录,再打开。
import shutil, tempfile, os
from openpyxl import load_workbook
src = r"X:/云盘/共享/月度报表.xlsx" # 挂载路径
tmp = os.path.join(tempfile.gettempdir(), "wb_tmp.xlsx")
shutil.copy2(src, tmp) # 几秒钟的事
wb = load_workbook(tmp, data_only=True)
从此所有涉及外部路径的读取都走这一道。多写两行,省下的是排查两小时。
三、列名不能靠切片拆
需要把单元格坐标拆成「列名 + 行号」,最常见的写法是 c[1:]:
coord = "AG7"
print(coord[1:]) # 得到 "G7"
# 后续调用直接抛:
# ValueError: 'G7' is not a valid column name
c[1:] 的隐含假设是「列名只有一位字母」。报表一宽,列就跑到 AA、AG、甚至 AAA 去了,这个假设立刻崩掉。而报错信息说的是 G7 不合法,很容易把人往错的方向带——你会去查 G7 这一行有什么问题,而不是去查切分逻辑。
用正则分组,列名和行号各归各位:
import re
coord = "AG7"
m = re.match(r"([A-Z]+)(\d+)", coord)
col, row = m.group(1), int(m.group(2))
print(col, row) # AG 7
顺带说一句,openpyxl 自己提供了 openpyxl.utils.column_index_from_string() 和 get_column_letter(),要做的是列号与字母互转,用官方的比手写稳。
写脚本处理财务数据,最贵的能力不是写得快,是知道自己什么时候在错。