不知道你有没有遇过这种糟心活儿: 公司KPI编码、物料编码长这样 —— CAFM 41、CAF 217、CAFMTS 33 规则说起来简单:后面的数字从右往左,一一对应到前面的字母位上;没分到数字的字母,再按固定公式补全分值。
真上手做就傻了:一行行拆字母、拆数字、对齐位置、算补全值,几百行数据做下来眼睛发花,对错位、算错数更是家常便饭。
今天给大家分享一个开箱即用的Python小工具,Excel丢进去自动完成全量映射,输出直接是美化好的表格,改3行配置就能适配你的业务,全程不用动核心代码。
一、开箱即用:这个工具能帮你做什么
整个脚本只有一百多行,主打一个「拿来就跑,跑了就用」,核心能力覆盖办公场景的绝大多数需求:
- 自动拆分:自动识别字符串里的字母段和数字段,空格、特殊符号自动过滤,数字逐位拆开
- 右对齐映射:数字从右往左对应字母位,严格匹配「末位数字对应最后一个字母」的业务规则
- 缺值自动补全:没有对应到数字的字母,自动按
(10 - 数字和) / 剩余字母数 公式补全,保留两位小数 - 配置化使用:所有参数集中在顶部CONFIG区,输入文件、列名、字母列表、Sheet页统统可改
- Excel友好输出:结果写入新Sheet,不覆盖原始数据;自带表头美化、全表边框、居中对齐、冻结首行,导出就能直接汇报
- 异常兜底:空值、空字符串、列名不存在都做了容错处理,不会跑一半崩掉
二、核心逻辑:字母数字映射是怎么实现的
整个工具的核心只有两个函数:拆分字符串 + 映射计算,我们逐一说透。
1. 字符串拆分:字母和数字彻底分开
defsplit_string_and_numbers(s):
if pd.isna(s):
return []
s = str(s).strip()
result = []
for p in re.split(r'(\d+)', s):
ifnot p:
continue
if p.isdigit():
for ch in p:
result.append(int(ch))
else:
letters = ''.join(c for c in p if c.isalpha())
if letters:
result.append(letters)
return result
关键设计讲解:
- 用正则
re.split(r'(\d+)', s) 按数字块切割字符串,括号表示保留切割符,一次性把「字母段」和「数字段」分开 - 数字段进一步拆成单个数字,比如
41 拆成 4 和 1,满足「一位数字对应一个字母」的业务要求 - 非数字段只保留纯字母,空格、横杠等杂符号自动过滤,兼容Excel里各种不规范的写法
举个例子,'CAFM 41' 经过拆分后,会得到 ['CAFM', 4, 1],为后续映射打好基础。
2. 映射计算:右对齐 + 缺值补全
这是整个工具最核心的业务逻辑,短短几十行覆盖了完整规则:
defmap_to_letters(s, header_letters=None):
parts = split_string_and_numbers(s)
letters_str = ''.join(p for p in parts if isinstance(p, str))
numbers = [p for p in parts if isinstance(p, int)]
result = {}
n_letters = len(letters_str)
n_numbers = len(numbers)
# 数字从右向左对应字母
for i, num in enumerate(numbers):
idx = n_letters - n_numbers + i
if0 <= idx < n_letters:
result[letters_str[idx]] = num
# 没数字的字母按公式补全
missing = [L for L in letters_str if L notin result]
if missing and numbers:
fill = round((10 - sum(numbers)) / len(missing), 2)
for L in missing:
result[L] = fill
return {L: result.get(L, '') for L in header_letters}
关键设计讲解:
右对齐核心公式:idx = n_letters - n_numbers + i 这行是实现「数字从右往左对应」的关键。比如字母有4个、数字有2个,第一个数字对应索引 4-2+0=2(第三个字母),第二个数字对应索引 4-2+1=3(第四个字母),刚好实现末位对齐,不会出现左对齐错位的问题。
缺值补全逻辑:先筛选出没分到数字的字母,只要存在数字就用 (10 - 数字总和) / 剩余字母数 计算补全值,保留两位小数,完全贴合KPI分值核算的常见规则。
输出对齐表头:最后按配置的字母列表返回结果,不在字符串里的字母自动留空,保证所有行的列顺序完全一致,不会出现列错位。
三、工程细节:为什么说它拿来就能用
一个工具好不好用,核心逻辑只占一半,剩下的全在细节里。
1. 配置集中化,零代码修改
脚本顶部专门留了CONFIG配置块,所有日常会改的参数全部集中在这里:
CONFIG = {
"input_path": "sample.xlsx", # 输入文件
"output_path": "result.xlsx", # 输出文件
"string_col": "字符串", # 待处理的列名
"header_letters": ['C', 'A', 'F', 'M', 'T', 'S'], # 输出字母列
"sheet_name": 0, # 输入Sheet
}
日常使用不用翻下面的代码,改完这几行直接运行,对非技术人员非常友好。
2. 智能写入,不破坏原文件
Excel写入做了两层判断:
- 输出文件已存在:追加新Sheet,同名Sheet自动替换 全程不会修改原始数据Sheet,算错了随时重跑,没有数据丢失风险。
3. 自动美化,省去排版时间
内置了 _beautify_sheet 函数,输出结果直接做好标准化排版:
- 预设列宽,避免内容挤在一起 生成的文件直接复制进汇报PPT都没问题。
4. 容错与自动适配
- 输入列不存在时,直接报错并列出所有可用列,不用瞎猜哪里错了
- 如果不指定字母列表,工具会自动扫描全表提取所有出现过的字母,按默认顺序输出,适配性更强
四、3步上手,不用改核心代码
第一步:安装依赖
只需要两个常用库,装一次就行:
pip install pandas openpyxl
第二步:修改配置
打开脚本,修改顶部CONFIG里的4个参数:
第三步:一键运行
命令行执行:
python letters_kpi_excel.py
运行后控制台会打印配置信息和结果预览,打开输出Excel就能看到「映射结果」Sheet,所有字母列的分值已经全部算好。
很多人觉得Python办公自动化很高深,其实绝大多数职场场景,都不需要复杂框架。像这种「固定规则的重复手工劳动」,几十行代码就能彻底替代,几分钟的工作量压缩到几秒,还能彻底避免人为失误。
完整代码已经全部放在文中,复制下来改个配置就能直接跑。下次再遇到拆编码、算映射的活儿,别再手动一行行抠了。
源码获取或交流
还需要本章或其他文章的源码和数据文件的同学,关注+三连,在对应文章下评论“6666“,加下面微信,发你!也可以拉你进群交流学习,加群备注:IT小本本学习
为了能随时获取最新动态,大家可以动动小手将公众号添加到“星标⭐”哦,点赞 + 关注,用时不迷路!!!!
关注公众号:IT小本本 👇
全部代码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
字母-数字映射工具 - 一键运行版
=====================================
直接修改下方 CONFIG 配置后运行:
python letters_kpi_excel.py
"""
import re
import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
# =========================================================
# ★ 在这里改配置(适合日常使用)
# =========================================================
CONFIG = {
"input_path": "sample.xlsx", # 输入文件
"output_path": "result.xlsx", # 输出文件
"string_col": "字符串", # 字符串列名
"header_letters": ['C', 'A', 'F', 'M', 'T', 'S'], # 字母列
"sheet_name": 0, # 输入 sheet(0=第一个)
}
# =========================================================
# 1. 核心算法
# =========================================================
defsplit_string_and_numbers(s):
"""
把字符串拆成 [字母段, 数字, 字母段, 数字, ...] 列表,每位数字都拆开
例:
'CAFM 41' → ['CAFM', 4, 1]
'CAF 217' → ['CAF', 2, 1, 7]
'CAFMTS 33' → ['CAFMTS', 3, 3]
"""
if pd.isna(s):
return []
s = str(s).strip()
ifnot s:
return []
result = []
for p in re.split(r'(\d+)', s):
ifnot p:
continue
if p.isdigit():
for ch in p:
result.append(int(ch))
else:
letters = ''.join(c for c in p if c.isalpha())
if letters:
result.append(letters)
return result
defmap_to_letters(s, header_letters=None):
"""
把字符串映射到字母列
规则:
1. 数字(每位拆开)从右向左对应字母
2. 没数字的字母按 (10-数字和)/无数字字母数 计算
"""
if header_letters isNone:
header_letters = ['C', 'A', 'F', 'M', 'T', 'S']
parts = split_string_and_numbers(s)
letters_str = ''.join(p for p in parts if isinstance(p, str))
numbers = [p for p in parts if isinstance(p, int)]
result = {}
n_letters = len(letters_str)
n_numbers = len(numbers)
# 数字 → 从右向左对应字母
for i, num in enumerate(numbers):
idx = n_letters - n_numbers + i
if0 <= idx < n_letters:
result[letters_str[idx]] = num
# 没数字的字母 → 公式填充
missing = [L for L in letters_str if L notin result]
if missing and numbers:
fill = round((10 - sum(numbers)) / len(missing), 2)
for L in missing:
result[L] = fill
return {L: result.get(L, '') for L in header_letters}
# =========================================================
# 2. Excel 读写
# =========================================================
defprocess_excel(input_path: str,
output_path: str,
string_col: str = "字符串",
header_letters: list = None,
sheet_name=0):
"""读取 Excel → 映射 → 写入新 sheet"""
df = pd.read_excel(input_path, sheet_name=sheet_name)
if string_col notin df.columns:
raise ValueError(
f"列 '{string_col}' 不存在!可用列: {list(df.columns)}")
# 自动推断字母表
if header_letters isNone:
all_letters = set()
for s in df[string_col].dropna():
parts = split_string_and_numbers(s)
letters_str = ''.join(p for p in parts if isinstance(p, str))
all_letters.update(letters_str)
default_order = ['C', 'A', 'F', 'M', 'T', 'S']
header_letters = [L for L in default_order if L in all_letters] \
or sorted(all_letters)
# 逐行映射
result_rows = [map_to_letters(s, header_letters) for s in df[string_col]]
result_df = pd.DataFrame(result_rows)
# 拼成最终表:原列 + 映射列
final_df = df.copy()
for L in header_letters:
final_df[L] = result_df[L].values
# 写入新 sheet
import os
ifnot os.path.exists(output_path):
# 输出文件不存在 → 直接写
with pd.ExcelWriter(output_path, engine="openpyxl") as writer:
final_df.to_excel(writer, sheet_name="映射结果", index=False)
else:
# 输出文件存在 → 追加 sheet
with pd.ExcelWriter(output_path, engine="openpyxl", mode="a",
if_sheet_exists="replace") as writer:
final_df.to_excel(writer, sheet_name="映射结果", index=False)
# 美化样式
_beautify_sheet(output_path, "映射结果", header_letters)
return final_df
def_beautify_sheet(file_path, sheet_name, header_letters):
"""给 sheet 加样式"""
wb = load_workbook(file_path)
ws = wb[sheet_name]
header_font = Font(name="微软雅黑", size=11, bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="2C3E50", end_color="2C3E50", fill_type="solid")
thin = Border(
left=Side(style="thin"), right=Side(style="thin"),
top=Side(style="thin"), bottom=Side(style="thin")
)
center = Alignment(horizontal="center", vertical="center")
for col in range(1, ws.max_column + 1):
c = ws.cell(row=1, column=col)
c.font = header_font
c.fill = header_fill
c.alignment = center
c.border = thin
for r in range(2, ws.max_row + 1):
for c in range(1, ws.max_column + 1):
cell = ws.cell(row=r, column=c)
cell.alignment = center
cell.border = thin
for col_letter, w in [("A", 16), ("B", 14), ("C", 8), ("D", 8),
("E", 8), ("F", 8), ("G", 8), ("H", 8)]:
ws.column_dimensions[col_letter].width = w
ws.freeze_panes = "A2"
wb.save(file_path)
# =========================================================
# 3. 一键运行( python)
# =========================================================
if __name__ == "__main__":
print("=" * 60)
print(" 字母-数字映射工具")
print("=" * 60)
print(f" 输入: {CONFIG['input_path']}")
print(f" 输出: {CONFIG['output_path']}")
print(f" 列名: {CONFIG['string_col']}")
print(f" 字母: {CONFIG['header_letters']}")
print()
result = process_excel(
input_path=CONFIG['input_path'],
output_path=CONFIG['output_path'],
string_col=CONFIG['string_col'],
header_letters=CONFIG['header_letters'],
sheet_name=CONFIG['sheet_name'],
)
print(f"\n 完成!已写入: {CONFIG['output_path']} → '映射结果'")
print(f"\n预览映射结果:")
print(result.to_string(index=False))