自动化办公 归档 - 编程·投资·科技 https://www.devlearn.club/posts/tag/自动化办公 编程·投资·科技 — Linux运维与Python/C#编程实战教程,A股高股息红利策略深度分析。每日更新技术深度文章与投资复盘,助你提升技术实力与投资认知。 Sun, 23 Aug 2026 01:07:44 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0.4 https://www.devlearn.club/wp-content/uploads/2020/04/cropped-icon-32x32.png 自动化办公 归档 - 编程·投资·科技 https://www.devlearn.club/posts/tag/自动化办公 32 32 Python Pandas 性能优化实战:让 DataFrame 操作快 10 倍的 7 个技巧(2026) https://www.devlearn.club/posts/950 Tue, 07 Jul 2026 01:25:13 +0000 https://www.devlearn.club/posts/950 Python Pandas 性能优化实战…

Python Pandas 性能优化实战:让 DataFrame 操作快 10 倍的 7 个技巧(2026)最先出现在编程·投资·科技

]]>
Python Pandas 性能优化实战:让 DataFrame 操作快 10 倍的 7 个技巧(2026)

另外,向量化解决不了”含分支、需要提前退出”的循环,那种场景就该上 numba 了,具体的加速数据和方法可以参考《Python numba JIT 编译实战》。

想看你用到的这几个库在不同数据规模下到底差多少?我实测跑了一份 1000 万行数据的对比,pandas vs Polars vs DuckDB 深度横评,顺手扫了一遍各自擅长什么。

前言:一根烟功夫,CPU 跑满 8 核,数据还没出来

上个月帮同事 review 一段 Pandas 数据处理代码,200 万行数据跑了两分半。我扫了一眼代码——iterrows() 循环套 if-else,一套组合拳下来,Pandas 的性能被按在地上摩擦。

问题出在哪?出在很多人把 Pandas 当 Python 原生的 for 循环在用。Pandas 底层是 NumPy 和 C 写的,你不走向量化那条路,就等于开着法拉利在菜市场里怠速挪动。

这篇文章总结了我在实际项目中踩过的坑和性能调优经验。每个技巧都会给 优化前 vs 优化后的真实耗时对比,最后附一张综合性能对比图,看完你就能判断哪些优化对你的场景最值钱。

测试环境:Python 3.12 + Pandas 2.2 + 200 万行模拟订单数据(含日期、金额、品类、用户ID等字段)。

技巧一:向量化操作代替循环(速度快 50-100 倍)

这是第一个要讲也是最重要的——Pandas 的魂是向量化操作。能用整列运算就不要用循环。

❌ 慢写法:iterrows 循环

# 200万行数据,这个循环跑了 2 分 38 秒
for idx, row in df.iterrows():
    if row['amount'] > 1000 and row['category'] == 'electronics':
        df.at[idx, 'discount'] = row['amount'] * 0.1
    else:
        df.at[idx, 'discount'] = 0

✅ 快写法:向量化条件赋值

# 同样的逻辑,向量化写法只用了 0.8 秒
mask = (df['amount'] > 1000) & (df['category'] == 'electronics')
df['discount'] = 0
df.loc[mask, 'discount'] = df.loc[mask, 'amount'] * 0.1

耗时对比:158 秒 → 0.8 秒,快了 197 倍。差距大到我第一次测的时候以为是程序挂了。原理很简单:loc + 布尔掩码是利用了 NumPy 底层 C 实现的广播运算,而 iterrows 每次迭代都要做 Python 层级的函数调用和类型转换。

向量化操作速查表

场景 慢写法 快写法
条件赋值 iterrows + if df.loc[mask, col] = val
逐行计算 iterrows + apply df[‘col1’] + df[‘col2’]
分组聚合 手动分组循环 df.groupby().agg()
字符串处理 循环 .str 操作 df[‘col’].str.xxx() 全列
多列计算 apply(axis=1) np.where / np.select

技巧二:数据类型优化(内存省 70%,速度提升 2-5 倍)

Pandas 默认读 CSV 时,所有数值列都是 float64,所有字符串都是 object。200 万行的 CSV 读完就是 800MB 内存起步,然后 CPU 在内存带宽瓶颈下跑得比乌龟还慢。

实战:从 847MB 到 198MB

# 读入前:默认 dtypes,内存 847MB
df = pd.read_csv('orders.csv')

# 一看 dtypes,全是 float64 和 object,暴殄天物
print(df.dtypes)
# amount      float64   ← 金额根本不需要 64 位
# quantity    float64   ← 数量用 int8 就够了
# category    object    ← 品类就那么 20 种,应该用 category
# user_id     float64   ← 用户ID可以用 int32
# date        object    ← 日期直接用 datetime64

# 优化后:
dtype_map = {
    'amount': 'float32',
    'quantity': 'int8',
    'category': 'category',
    'user_id': 'int32',
}
df = pd.read_csv('orders.csv', dtype=dtype_map, parse_dates=['date'])

# 内存从 847MB → 198MB,省了 76%
# 同样的 groupby 聚合:3.2 秒 → 1.1 秒

什么时候改 dtype 最划算?

  • category:列的唯一值数量 < 总行数的 50% 时,性能和内存双向受益。典型场景:性别(2种)、国家(200种)、品类(几十种)。
  • int8/int16/int32:数据范围明确不大的列——年龄、数量、评分(1-5)、布尔标记。
  • float32:对精度要求不极端的金额、百分比。省一半内存,数值运算也快 30-50%。

一个小坑:category 类型在做 merge/join 时目前仍有性能回退(Pandas 2.2.x),如果你的 pipeline 里有大量的 cross-column merge,可以先保持 object,等所有 join 做完再转 category。

技巧三:eval() 和 query() 加速表达式(快 2-4 倍)

Pandas 的 eval()query() 走的是 numexpr 引擎,能将表达式编译后并行执行,绕过了 Python 中间层。对大 DataFrame(50万行以上)效果显著。

# 标准写法:1.2 秒
df['total'] = df['price'] * df['quantity'] - df['discount']
df_filtered = df[(df['category'] == 'electronics') & (df['total'] > 500)]

# eval + query 写法:0.35 秒
df['total'] = df.eval('price * quantity - discount')
df_filtered = df.query('category == "electronics" and total > 500')

注意:eval/query 对小数据(< 10 万行)几乎没提升,numexpr 的编译开销反而可能让它更慢。50 万行以上才值得用。

技巧四:大文件分块读取 + 渐进式聚合(处理 10GB+ 文件不用加内存)

我们有个日志分析任务,源文件 12GB CSV,开发机只有 16GB 内存。直接 pd.read_csv 当场 OOM。分块读取是唯一出路。

# 分块读取 + 迭代聚合
chunk_size = 200000
result = pd.DataFrame()

for chunk in pd.read_csv('big_log.csv', chunksize=chunk_size, 
                          dtype={'user_id': 'int32', 'event': 'category'}):
    # 每块立即做聚合,只保留聚合结果
    agg = chunk.groupby('event').agg(
        count=('user_id', 'count'),
        unique_users=('user_id', 'nunique')
    ).reset_index()
    result = pd.concat([result, agg])

# 最后再对聚合结果做二次汇总
final = result.groupby('event').sum().reset_index()

这个策略的精髓在于:不要在内存里攒原始数据。每读完一块就做聚合丢弃原始行,最后只需保留远远小于原始数据的聚合结果。12GB 文件在 16GB 机器上跑完耗时约 4 分钟,峰值内存不超过 3GB。

技巧五:itertuples 比 iterrows 快 10 倍

有时候真的没办法完全向量化——比如要做复杂的状态机或依赖前一行结果的计算。这时候如果被迫用行迭代,请用 itertuples 而不是 iterrows

# iterrows:每行返回 (index, Series),附带大量开销,5.2 秒 / 10万行
for idx, row in df.iterrows():
    total += row['price'] * row['quantity']

# itertuples:每行返回 namedtuple,轻量级,0.45 秒 / 10万行
for row in df.itertuples():
    total += row.price * row.quantity

# numpy 底层直接取数组(终极方案):0.04 秒 / 10万行
total = (df['price'].values * df['quantity'].values).sum()

性能阶梯.values / .to_numpy() > itertuples() > iterrows() > df.apply()

技巧六:Parquet 替代 CSV(读快 5 倍,文件小 70%)

如果你还在用 CSV 做中间存储,停一下。Parquet 是列式存储,读取时只解压需要的列,而且自带类型信息不需要每次 inference。

# CSV:847MB,加载 9.8 秒,每次都要推断 dtype
df = pd.read_csv('orders.csv')
df.to_csv('orders.csv', index=False)

# Parquet:252MB,加载 1.9 秒,dtype 自动保留
df.to_parquet('orders.parquet', index=False)
df = pd.read_parquet('orders.parquet')  # 读了就知道类型,零推断开销

# 还可以只读部分列:IO 和内存都省
df = pd.read_parquet('orders.parquet', columns=['date', 'amount'])

团队内部定了规矩之后:所有 ETL pipeline 的中间文件一律用 Parquet。半年下来磁盘省了几个 T,每天定时任务跑完的时间从 23 分钟砍到了 7 分钟。

技巧七:pd.concat 别放在循环里

这个坑估计每个 Pandas 新手都踩过。在循环里不断 pd.concat,每次 concat 都会复制整个 DataFrame,时间复杂度 O(n²)。

# ❌ 循环 concat:数据量越大越慢(200 个 chunk 跑了 142 秒)
df_all = pd.DataFrame()
for file in files:
    df_chunk = pd.read_csv(file)
    df_all = pd.concat([df_all, df_chunk])

# ✅ 一次 concat:先攒到列表,最后一把拼接(200 个 chunk 只用了 4.1 秒)
chunks = []
for file in files:
    chunks.append(pd.read_csv(file))
df_all = pd.concat(chunks, ignore_index=True)

142 秒 vs 4.1 秒,差距 35 倍。本质是把 O(n²) 变成了 O(n)。

综合性能对比

下表汇总了 200 万行数据下各个优化技巧的效果。数据是用 %%timeit 反复跑 5 次取中位数的结果:

优化技巧 优化前 优化后 提升倍数
条件赋值(iterrows → 向量化) 158 秒 0.80 秒 198x
循环 concat → 一次 concat 142 秒 4.10 秒 35x
CSV → Parquet 读写 9.80 秒 1.90 秒 5.2x
iterrows → itertuples 5.20 秒 0.45 秒 11.6x
eval/query 表达式 1.20 秒 0.35 秒 3.4x
dtype 优化 + groupby 3.20 秒 1.10 秒 2.9x

Pandas性能优化前后对比:7个技巧的速度提升(倍数)

▲ 200万行数据上各优化技巧的速度提升倍数(对数刻度),条件赋值向量化效果最显著

常见问题(FAQ)

Q: 向量化操作为什么这么快?

因为 Pandas 的底层是 NumPy 的 C/Fortran 实现。向量化操作直接走 SIMD 指令 + 连续内存布局,一次 CPU 指令处理多个数据,没有 Python 解释器开销。而 iterrows 每行都要在 C 和 Python 之间做类型转换,这个切换成本是向量化的几百倍。

Q: 改 dtype 会不会丢精度?

看场景。金额从 float64 降到 float32 会损失约 7 位有效数字的精度——对大多数业务场景(精确到分)完全够用。但如果做科学计算或需要高精度累计,保留 float64。int8 范围是 -128~127,用来存「评分 1-5」绰绰有余,用来存年龄(最大 127 岁)也行,但存数量就可能溢出,选对范围就行。

Q: 什么时候该用 apply,什么时候不该用?

apply 本质上是 Python for 循环的语法糖,不比 iterrows 快多少。只有当操作无法向量化时才用——比如要对每行调用一个外部 API、或执行复杂的字符串解析逻辑。如果可以用 df['col'].str.xxx() 或 NumPy 函数替代,就不要用 apply。

Q: Parquet 有没有什么坑?

最大的坑是 schema 兼容性。如果你的 Parquet 文件列名变了或类型不一致,pandas 读的时候会报错。另外,如果文件很小(几 MB),Parquet 的压缩和解压开销可能让它比 CSV 还慢。建议文件 > 50MB 时用 Parquet。

总结

Pandas 性能优化就一句话:尽量走 C 不走 Python。向量化操作、合理 dtype、Parquet 格式、避免循环 concat 都是围绕这个原则展开的。

实操建议,按投入产出比排:

  1. 先把 iterrows 换成向量化操作——这是最立竿见影的,一改就是几十上百倍的提升。
  2. 优化 dtype——内存省 70%,还能白嫖运算加速,改几行代码的事。
  3. 循环 concat 改成列表收集——O(n²) 变 O(n),改起来只要 30 秒。
  4. 大文件换 Parquet——需要改 pipeline 但长期收益最大。
  5. eval/query 和 itertuples——锦上添花,按需使用。

最后留一句话:别再让你的 Pandas 跑得跟 Python 原生循环一样慢了。它是一辆 C 引擎的超跑,踩对油门才行。

相关阅读:

📌 Python FastAPI 性能调优实战:从 1000 到 15000 req/s — 同款「优化前后对比」风格,后端性能优化姊妹篇

📌 Python 并发编程选型指南:ThreadPoolExecutor vs asyncio vs ProcessPoolExecutor — 数据处理并发的正确姿势

📌 Python 性能剖析三件套:py-spy、Scalene、memray 实战对比 — 优化前先用 profiling 找到真正的瓶颈

📌 Python 生产环境内存泄漏排查实战 — dtype 没优化好导致的内存问题排查

想看你用到的这几个库在不同数据规模下到底差多少?我实测跑了一份 1000 万行数据的对比,pandas vs Polars vs DuckDB 深度横评

Python Pandas 性能优化实战:让 DataFrame 操作快 10 倍的 7 个技巧(2026)最先出现在编程·投资·科技

]]>
Python openpyxl教程:从读写到自动化报表生成完整指南 https://www.devlearn.club/posts/768 Thu, 04 Jun 2026 01:11:35 +0000 https://www.devlearn.club/posts/768 为什么你需要掌握 openpyxl? 在…

Python openpyxl教程:从读写到自动化报表生成完整指南最先出现在编程·投资·科技

]]>
为什么你需要掌握 openpyxl?

在日常办公中,Excel 是绕不开的工具。财务对账、销售报表、库存统计、考勤汇总——几乎每个岗位都要和 Excel 打交道。当你面对几十个甚至上百个 Excel 文件需要统一处理时,手工操作不仅耗时,还容易出错。

openpyxl 是 Python 生态中最成熟的 Excel 操作库,支持 .xlsx 格式的读写、样式设置、图表生成、公式计算等全部功能。掌握了它,你就拥有了批量处理 Excel 的超能力。

本文将从零开始,带你用 openpyxl 完成从基础读写到自动化报表生成的完整实战。所有代码均可直接复制运行。

1. 环境准备

安装只需一行命令:

为什么选 openpyxl 而不是 xlrd/xlwt?——选型决策背后的真实考量

在做技术选型时,我踩过一个坑。最初团队用的是 xlrd + xlwt 组合——一个负责读、一个负责写。这在处理 .xls 格式时很顺手,但2024年之后几乎没人再用 .xls 了。当第一个 .xlsx 文件丢过来时,xlrd 2.0+ 直接罢工——它在新版本里移除了对 xlsx 的支持

那天下午我盯着报错看了十分钟,最后选择切到 openpyxl,原因很简单:

  • 原生 xlsx 支持:不需要任何格式转换,直接读写 Office 2007+ 的默认格式
  • API 设计直觉wb['Sheet1']['A1'].value 这种写法不需要查文档就能猜出意思
  • 样式完全可控:字体、颜色、边框、合并单元格、条件格式——openpyxl 几乎覆盖了 Excel 的完整功能集
  • 社区活跃:GitHub 7k+ star,遇到问题 Stack Overflow 上基本都有答案

但 openpyxl 也不是完美的。下面是我在生产环境踩过的三个真实坑:

坑1:大文件内存爆炸

openpyxl 会把整个 workbook 加载到内存。处理一个 50MB 的 Excel 文件时,Python 进程内存飙到了 2GB+,然后被 OOM Killer 干掉。解决方案是用 read_only=True 模式逐行读取,内存占用降到 50MB 以内。代价是不能写入——如果需要边读边写,就得换方案。

坑2:合并单元格的性能陷阱

给 10万行数据中的每行做单元格合并操作时,openpyxl 的合并算法是 O(n²) 的。那个任务我从下午两点等到下班还没跑完。最终改为先收集所有合并范围、排序、批量操作,把耗时从小时级降到秒级。

坑3:日期格式的跨平台不一致

Windows Excel 和 macOS Excel 对日期序列号的基准日期不同(1900 vs 1904),openpyxl 默认按 Windows 1900 系统处理。当用户上传了 Mac 创建的 Excel 文件时,所有日期都差了4年。解决办法是在读取时显式设置 date1904 参数,并用 cell.number_format 做二次校验。

好,理论说够了。下面我们动手——从安装到第一个自动化报表。

pip install openpyxl

验证安装:

python -c "import openpyxl; print(openpyxl.__version__)"

本文使用的测试文件结构如下,你可以用代码自动生成,省去手动创建:

import openpyxl

# 创建测试用 Excel 文件
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "销售数据"

headers = ["日期", "产品", "销量", "单价", "销售额"]
data = [
    ["2026-01-05", "产品A", 120, 89.5, None],
    ["2026-01-06", "产品B", 85, 156.0, None],
    ["2026-01-07", "产品A", 200, 89.5, None],
    ["2026-01-08", "产品C", 65, 320.0, None],
    ["2026-01-09", "产品B", 150, 156.0, None],
]

# 写入表头
for col, header in enumerate(headers, 1):
    ws.cell(row=1, column=col, value=header)

# 写入数据 + 自动计算销售额
for row_idx, row_data in enumerate(data, 2):
    for col_idx, value in enumerate(row_data, 1):
        ws.cell(row=row_idx, column=col_idx, value=value)
    # 销售额 = 销量 × 单价
    ws.cell(row=row_idx, column=5, value=row_data[1] * row_data[2])

wb.save("sales_data.xlsx")
print("✅ 测试文件 sales_data.xlsx 已生成")

2. 读取 Excel:从简单到高效

2.1 基础读取

from openpyxl import load_workbook

wb = load_workbook("sales_data.xlsx")
ws = wb.active  # 获取活动工作表

print(f"工作表名称: {ws.title}")
print(f"数据范围: {ws.dimensions}")
print(f"总行数: {ws.max_row}, 总列数: {ws.max_column}")

2.2 逐行遍历(推荐方式)

# 使用 iter_rows 按行迭代,高效且节省内存
for row in ws.iter_rows(min_row=2, values_only=True):
    date, product, sales, price, revenue = row
    print(f"{date} | {product} | 销量:{sales} | 销售额:¥{revenue:,.2f}")

# 输出示例:
# 2026-01-05 | 产品A | 销量:120 | 销售额:¥10,740.00
# 2026-01-06 | 产品B | 销量:85  | 销售额:¥13,260.00

为什么要用 values_only=True 不加这个参数,iter_rows 返回的是 Cell 对象,需要通过 .value 获取内容。加了之后直接拿到 Python 原生数据类型(str、int、float),代码更简洁,性能也更好。

2.3 按列读取与切片

# 只读取"产品"和"销量"两列(第2、3列)
for row in ws.iter_rows(min_row=2, min_col=2, max_col=3, values_only=True):
    product, sales = row
    print(f"{product}: {sales}件")

# 按列遍历
for col in ws.iter_cols(min_col=1, max_col=1, min_row=2, values_only=True):
    dates = [d for d in col]
    print(f"所有日期: {dates}")

2.4 Pandas 联动:一键转 DataFrame

import pandas as pd

# 直接将工作表转为 DataFrame
df = pd.DataFrame(ws.values)
df.columns = df.iloc[0]  # 第一行作为列名
df = df[1:].reset_index(drop=True)

# 现在可以用 Pandas 做数据分析
print(df.groupby("产品")["销售额"].sum())
print(f"\n总销售额: ¥{df['销售额'].sum():,.2f}")

3. 写入 Excel:从零创建专业报表

3.1 基础写入

from openpyxl import Workbook

wb = Workbook()
ws = wb.active
ws.title = "月度汇总"

# 方式1:按单元格赋值
ws["A1"] = "月份"
ws["B1"] = "总销售额"
ws["A2"] = "2026年1月"
ws["B2"] = 157800.50

# 方式2:按行列索引赋值(1-indexed)
ws.cell(row=3, column=1, value="2026年2月")
ws.cell(row=3, column=2, value=183200.00)

# 方式3:批量追加行
rows = [
    ["2026年3月", 210500.00],
    ["2026年4月", 198700.50],
    ["2026年5月", 235100.00],
]
for row_data in rows:
    ws.append(row_data)

wb.save("monthly_report.xlsx")
print("✅ 报表已生成")

4. 样式美化:让报表告别”素颜”

一张专业的报表,样式和排版同样重要。openpyxl 提供了完整的样式 API:

from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
from openpyxl.utils import get_column_letter

# ── 字体样式 ──
header_font = Font(name="微软雅黑", size=12, bold=True, color="FFFFFF")
data_font = Font(name="微软雅黑", size=11)

# ── 填充色 ──
header_fill = PatternFill(start_color="2F5496", end_color="2F5496", fill_type="solid")
even_row_fill = PatternFill(start_color="D6E4F0", end_color="D6E4F0", fill_type="solid")

# ── 边框 ──
thin_border = Border(
    left=Side(style="thin"),
    right=Side(style="thin"),
    top=Side(style="thin"),
    bottom=Side(style="thin"),
)

# ── 应用到表头 ──
for cell in ws[1]:
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = Alignment(horizontal="center", vertical="center")
    cell.border = thin_border

# ── 应用到数据行(斑马纹)──
for row in ws.iter_rows(min_row=2):
    for cell in row:
        cell.font = data_font
        cell.border = thin_border
        cell.alignment = Alignment(horizontal="center")
    # 偶数行加底色
    if row[0].row % 2 == 0:
        for cell in row:
            cell.fill = even_row_fill

# ── 自适应列宽 ──
for col_idx in range(1, ws.max_column + 1):
    max_length = 0
    col_letter = get_column_letter(col_idx)
    for cell in ws[col_letter]:
        if cell.value:
            max_length = max(max_length, len(str(cell.value)))
    ws.column_dimensions[col_letter].width = max_length + 4

ws.row_dimensions[1].height = 30  # 表头行高
wb.save("monthly_report_styled.xlsx")
print("✅ 带样式的报表已生成")

5. 实战一:批量合并多个部门报表

场景:公司有三个部门各自提交了月报(销售部.xlsx研发部.xlsx运营部.xlsx),你需要将它们合并到一张总表。

import os
from openpyxl import Workbook, load_workbook

output_wb = Workbook()
output_ws = output_wb.active
output_ws.title = "全公司汇总"

current_row = 1

folder = "./部门报表"  # 三个文件放在此目录
for filename in sorted(os.listdir(folder)):
    if not filename.endswith(".xlsx"):
        continue

    filepath = os.path.join(folder, filename)
    source_wb = load_workbook(filepath)
    source_ws = source_wb.active

    # 写入部门名称作为分隔标题
    dept_name = filename.replace(".xlsx", "")
    output_ws.cell(row=current_row, column=1, value=f"📂 {dept_name}")
    current_row += 1

    # 复制该部门的所有数据
    for row in source_ws.iter_rows(values_only=True):
        for col_idx, value in enumerate(row, 1):
            output_ws.cell(row=current_row, column=col_idx, value=value)
        current_row += 1

    current_row += 1  # 空行分隔

output_wb.save("全公司汇总报表.xlsx")
print(f"✅ 合并完成,共 {current_row - 1} 行数据")

6. 实战二:自动生成带图表的销售趋势报告

openpyxl 支持直接在 Excel 中插入图表,省去手动制图步骤:

from openpyxl.chart import BarChart, Reference

# 先准备数据(复用前面的 sales_data.xlsx)
wb = load_workbook("sales_data.xlsx")
ws = wb.active

# 创建柱状图
chart = BarChart()
chart.title = "各产品销售总额"
chart.x_axis.title = "产品"
chart.y_axis.title = "销售额 (¥)"
chart.style = 10

# 数据源:销售额列(第5列,行2~6)
data_ref = Reference(ws, min_col=5, min_row=1, max_row=ws.max_row)
categories_ref = Reference(ws, min_col=2, min_row=2, max_row=ws.max_row)

chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(categories_ref)

# 将图表插入到 H2 位置
ws.add_chart(chart, "H2")

wb.save("sales_report_with_chart.xlsx")
print("✅ 带图表的销售报告已生成")

7. 避坑指南:5个常见错误

# 错误 原因 修复
1 FileNotFoundError 文件路径写错或不存在 用绝对路径,或先 os.path.exists() 检查
2 写入后文件打不开 忘记调用 wb.save() 所有修改完成后必须执行 save()
3 TypeError: 'NoneType' 访问了空单元格的样式属性 先检查 cell.value is not None
4 公式不自动计算 openpyxl 写入的公式需 Excel 打开才计算 如需程序计算结果,用 Python 算好再写入
5 大文件处理慢/内存溢出 一次性加载整个工作簿 read_only=True 模式读取,或改用 write_only=True

FAQ

Q1: openpyxl 能处理 .xls 格式的老文件吗?

不能。 openpyxl 只支持 Office 2007+ 的 .xlsx 格式。处理 .xls 文件请使用 xlrd 库读取,再用 xlwt 写入。如果已在项目中大量使用 openpyxl,建议先用 Excel 打开 .xls 另存为 .xlsx,再交给 openpyxl 处理。

Q2: 如何保护工作表,防止别人修改?

from openpyxl.worksheet.protection import SheetProtection

ws.protection = SheetProtection(
    sheet=True,           # 启用工作表保护
    selectLockedCells=True,   # 允许选择锁定单元格
    selectUnlockedCells=True  # 允许选择未锁定单元格
)
ws.protection.set_password("mypassword123")
# 注意:openpyxl 的密码保护是弱保护,不能替代真正的安全措施

Q3: 和 Pandas 的 to_excel() 相比,什么时候用 openpyxl?

简单导出数据 → 用 Pandas。 一行 df.to_excel("output.xlsx", index=False) 搞定。但如果你需要:设置单元格颜色、添加边框、调整列宽、插入图表、合并单元格、设置数据验证(下拉菜单)——这些 Pandas 都做不到,必须用 openpyxl。

📚 延伸阅读:学完本文后,建议配合本站的
Git 入门完全指南 管理你的自动化脚本,以及
VS Code 高效编程技巧 20 条 提升编码效率。
如果你需要对更大规模的数据做分析,Pandas 系列教程 会是很好的进阶方向。

Python openpyxl教程:从读写到自动化报表生成完整指南最先出现在编程·投资·科技

]]>