尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用Python脚本自动生成自然码双拼辅码表
自然码、双拼、辅码表、Python 脚本、手心输入法这几个词凑在一起的时候说明你已经不满足于能打字而是想把选字这一步也压到极致。双拼让一个汉字平均两键落地但同音字太多shi这个音能翻出上百个候选手指在数字键上来回摸的那一下节奏就断了。自然码体系的辅码就是冲着这个断点去的——第三键敲部首的声母候选从几十个直接缩到个位数。问题是辅码表得有人把两万多个常用字一个一个查出部首、再转成字母手工干这事不仅枯燥还极容易错。这篇内容就是把这件苦力活交给 Python 脚本一次跑完、随时可改、可复现。适合三类人看天天用双拼想进一步提速的老用户、喜欢自己折腾输入法配置的人以及想找一个真实中文数据处理练手项目的 Python 学习者。全程只用标准库不装第三方包二十分钟能跑通。1. 先把问题想明白辅码表到底在解决什么1.1 双拼的速度瓶颈其实不在按键在选字很多人以为双拼的爽点是两键一个字用久了会发现真正的爽点不在这里。双拼的优势是击键少但汉语的音节总数只有四百来个不计声调加上声调也就一千多而常用汉字有三千五全部汉字超过两万。这就注定了同音字是绕不开的——按完双拼码之后屏幕上是一排候选字你得用数字键或者翻页去挑。一段两百字的短文如果平均每五个字要选一次那就是四十次额外的按键比打字本身还累。我自己的体感是纯双拼打日常聊天还行一旦进入写稿、写文档这种连续输出的场景节奏感被选字打断得很难受。手指在字母区高速移动的时候突然要去够上面的数字键这个动作切换的成本远比想象中高。辅码就是为了消掉这个切换。它的思路很朴素既然拼音层面区分不开那就再加一个维度用字形来区分。你敲完双拼码之后再敲一个和这个字字形相关的字母候选列表立刻塌缩。这个第三个键就是辅码也叫形码辅助码。自然码体系里这个设计做得比较早也比较成熟所以到今天我们谈辅码基本还是沿着自然码的思路走。1.2 自然码辅码的取码逻辑本质是部首读音首字母自然码辅码的规则说起来一句话就能讲完取这个字的部首把部首当成一个独立的汉字来读取它的声母。比如张双拼码是 zh ang辅码取部首弓的声母 g三键下去基本就只剩张一个候选了。海的部首是氵它本身不是字但它是水的变形读 shui取 s。李的部首是木取 m。想的部首是心取 x。为什么选部首而不是选笔画因为部首在字典里是现成的元数据而且区分度够高。汉字的部首有 200 多个取声母之后归成 20 来个字母每个字母底下又对应十几个部首但已经足够把同音字的候选从几十个砍到三五个。如果改用笔画横竖撇捺折一共五种取首笔的话信息量太少同码率还是很高取多笔又记不住学习成本陡增。部首这条路是记忆成本和区分效果之间比较平衡的位置。再往上一层自然码还有二级辅码意思是一级辅码敲完还有重码就继续拆剩下的部分再取一次部首的声母。实际使用中一级辅码已经能解决九成以上的场景二级属于锦上添花。我这份脚本先做一级结构上留好扩展位。1.3 手工整理这条路为什么走不通假设你要覆盖国标一级加二级汉字大概 6700 多个字。每个字要经历确定它的部首是哪个→确认这个部首读什么音→转成声母三步。最麻烦的是第一步因为一个字的部首归属并不是天然清晰的。比如和现代字典一般归口部但古文字里它跟禾关系更近颖归页部还是禾部不同字典处理不一样。人在做这件事的时候判断标准会漂移做到第五百个字的时候和第一千个字的时候标准可能就不一样了。更现实的问题是迭代成本。你今天用一级辅码明天想加二级后天想把某个部首的取音换掉——手工表改起来就是灾难改一处就得全表复查。脚本就不一样了规则写在代码里改一行、跑一次、三秒钟出新表。这个差距不是效率问题是能不能持续维护的问题。很多人自己做的码表用了半年就废了不是因为方案不好是因为改不动。所以我的判断很明确只要涉及超过一千条数据的规则化映射就应该写脚本哪怕只用一次。因为只用一次这个假设基本上不成立。2. 数据源怎么选汉字到部首那一层从哪来2.1 四条可选路线横向对比一下在动手写代码之前把数据来源定下来是关键这一步选错了后面全是返工。我把能想到的路子列出来做了个对比路线数据量级准确度获取成本可维护性主要问题手工整理部首归属需要覆盖 6700 字依赖个人标准中极高差判断标准会漂移改一次全表复查在线字典接口逐个查一次一查几千次请求高中受网络与接口变动影响容易被限流结果格式不稳定现成形码表反推拿到即可用取决于原作者低极差拿不到规则改不了也不一定适配你的方案统一汉字数据库Unihan全量字符集高来源权威低好需要自己写一层解读逻辑我的选择是第四条。理由很直接它是结构化的、有明确字段定义的、覆盖全字符集的、可以离线跑的数据。这条路唯一的门槛是你得读懂它的字段含义而这一步只需要十分钟。2.2 kRSUnicode 字段到底给了什么信息Unihan 数据库里有一张表专门描述汉字的部首信息字段名叫kRSUnicode它在文件里长这样U5F20 kRSUnicode 57.3 U674E kRSUnicode 75.3 U6D77 kRSUnicode 85.7 U60F3 kRSUnicode 61.9第一列是码位U 开头第二列是字段名第三列是值。值的格式是部首序号.剩余笔画数中间可能带一个撇号。比如57.3表示这个字的部首是第 57 号部首去掉部首之后还剩 3 画。查一下 214 康熙部首的顺序表第 57 号是弓正好对上张字。这个部首序号就是全部的关键。214 个部首是固定的、有序的、全世界通用的每个汉字在这套体系里都有且只有一个归属。这意味着你根本不需要去判断这个字该归哪个部标准已经替你判断好了——这一点比手工整理强太多。值里那个撇号值得单独说一下。有些条目会写成9.0这种形式撇号表示这是一个变体部首通常是简化字用到的写法。比如亻在 Unihan 里会归到第 9 号部首人但带撇号标记说明它是变形写法。这个标记对我们来说其实不用区分因为亻和人的读音首字母都是 r映射到同一个字母就完了。所以脚本里直接把撇号吃掉只取数字部分。还有一个细节同一行里可能出现多个值用空格分隔比如9.0 9.3。这时候取第一个就行因为第一个是主部首。这个坑我第一次就踩了用严格的整行匹配会漏掉大量条目后来改成搜索模式才对。2.3 把部首序号翻译成读音首字母用两张字符串搞定拿到部首序号之后还差一步序号 → 部首字 → 读音 → 声母。最笨的办法是写 214 个 if-else但更省事的做法是把 214 个部首按顺序拼成一个字符串再把对应的 214 个声母按同样顺序拼成另一个字符串两个字符串用zip一配就完事了。RADICALS ( 一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士 夂夊夕大女子宀寸小尢尸屮山巛工己巾干幺广廴廾弋弓彐彡彳心戈戶手支攴 文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘 生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自 至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉釆 里金長門阜隶隹雨青非面革韋韭音頁風飛食首香馬骨高髟鬥鬯鬲鬼魚鳥鹵鹿 麥麻黃黍黑黹黽鼎鼓鼠鼻齊齒龍龜龠 ) INITIALS ( ygzpyejtre rbjmbjqdlb bfxsbjcsyk wtszsxdnzm cxwscscgjj gygygygjsc xghszpwdjf wryymqzdsw bmsqshzfyp pynqxygwgs ytpnbbpmmm sssrhxlzms fwyylyelyr czzjsczgsc hcxxyyjjyg dszbczzscx czyybljcmf lzyqfmgwjy yffssxmggb dclgynllmm hshzmdgsbq clgy ) assert len(RADICALS) 214, 部首串抄漏了 assert len(INITIALS) 214, 声母串抄漏了 RADICAL_INITIAL {i 1: s for i, s in enumerate(INITIALS)}这两串东西看起来吓人但胜在透明。你想改哪个部首的取音直接定位到那个位置改一个字母就行不用去翻结构体。这是我最喜欢这种写法的地方——数据和逻辑分离得很干净。关于多音部首怎么取音我现在的处理原则是取最常用的那个读音。比如第 85 号部首水读 shui取 s第 130 号肉它在月字旁里出现时会读 rou所以取 r 而不是 y。第 170 号阜取 f第 163 号邑取 y——这两个在字形上都写成阝但左耳旁是阜右耳旁是邑Unihan 已经分好了你不用管位置判断。这里有个取舍要讲清楚辅码表是给手感用的不是给正音用的。如果某个部首的规范读音很生僻但用户习惯按另一个音来记那就应该按用户习惯来。比如第 104 号疒规范读音是 nè但绝大多数人第一反应是病字旁脑子里蹦出来的是 b。这种情况下我倾向保留 n因为如果改成 b会和白贝比等一堆 b 打头的部首撞车反而降低区分度。规则要服务于效果不能只服务于正确。3. 取码规则落地把自然码辅码的细节啃下来3.1 一级辅码的主干链路只有四步把整个映射链路捋直了其实很短读文件拿到码位和部首序号 → 码位转字符 → 部首序号查声母 → 写入字典。四步没有分支没有例外处理。这就是我推荐用 Unihan 的原因——主干逻辑干净到几乎不需要调试。真正需要费心的全在边上比如字符范围过滤、重复码处理、格式对齐。这些属于工程问题不是规则问题。我在写的时候把主干和边角严格分开主干函数不超过 20 行一眼能看完剩下的都是可插拔的后处理。这么做的原因是辅码规则以后一定要改改的时候你只希望动主干那一小块而不是在一个两百行的大函数里翻找。还有一个隐含前提值得点明Unihan 保证每个汉字都有部首所以理论上不存在某个字查不到辅码这种情况。这一点非常关键它意味着你的码表可以做到全覆盖。这和某些形码方案不一样那些方案里独体字、生僻字常常需要特殊规则兜底而这里不需要。3.2 变形部首和简繁差异的处理边界变形部首的情况Unihan 已经消解掉绝大部分了。氵归水、扌归手、忄归心、讠归言、纟归糸、钅归金、饣归食、衤归衣、犭归犬——这些简化偏旁全都已经映射到对应的正体部首序号上了。也就是说你不需要自己维护一张偏旁对应表这是 Unihan 帮你省下的最大一块工作量。但有几种情况还是得自己拿主意。第一种是月字旁它在肌肝肚里其实是肉的变形Unihan 会给第 130 号肉取 r。而在明期朝里它是真的月给第 74 号取 y。这个区分 Unihan 做对了你只要保证 74 和 130 两个序号的声母不同就行——y 和 r没问题。第二种是繁体字。如果你只做简体用户可以直接把基本区里那些高频繁体字过滤掉但我不建议这么干因为很多用户会在输入法里挂繁体词库繁体字没辅码会很难受。更省事的做法是全部保留反正一个字的辅码就是查表多一条不占多少空间。整个过程对两万多个字来说内存占用也就是几兆的事。第三种是扩展区的生僻字。基本区是 U4E00 到 U9FFF六万多个码位实际编码了大约两万多个汉字。扩展区还有 B 到 G 好几万个字如果全量导出码表会膨胀到十几万行导入输入法的时候可能卡顿。我的建议是只做基本区够用了。3.3 独体字和没部首的兜底策略前面说了 Unihan 保证每个字都有部首所以严格意义上不存在没部首的字。但存在另一种情况部首查出来了声母也转了但这个辅码不好记或者和别的字大面积撞车。比如一这个字它的部首就是一取 y。而所有部首为一的字——丁七万三上下不与——辅码全是 y。这一大片全是 y区分度约等于零。类似的还有丶部、丿部这些部首在 214 体系里用来归类那些实在看不出结构的字本身没什么信息量。面对这种情况有三条路一是接受它反正这些字本身出现频率高候选排序靠前问题不大二是给这些低信息部首单独换一套取码规则比如改用首笔画三是直接不给这些字生成辅码让输入法回落到默认行为。我选第一条理由很实在辅码的作用是减少选字次数不是一次都不选。哪怕辅码区分度低只要它能把候选缩一半收益就已经是正的了。为了少数几个难搞的字去加一套复杂规则维护成本远超收益。这就是典型的工程取舍——完美是好的敌人。如果你确实想加兜底我建议的方案是给这批低区分度部首的首字母做一个二级分流比如按剩余笔画数的奇偶拆成两个字母。但这属于进阶玩法先跑通一级再说。4. Python 脚本实操完整跑通一遍4.1 环境准备其实没什么可准备的Python 3.8 以上就行不需要虚拟环境不需要 pip 装任何东西全程标准库re做解析、pathlib处理路径、collections.Counter做统计。文件目录就两个文件project/ ├── gen_aux_code.py └── Unihan_IRGSources.txtUnihan_IRGSources.txt从公开的统一汉字数据库下载包里解压出来压缩包解开之后有一堆 txt你只需要这一个。它体积不小两百多兆但我们要的字段只占其中很小一部分所以读取的时候必须做行级过滤不然光解析就得等半天。一个提醒这个文件里的字段是按字母序排的kRSUnicode大概在整份文件的中后段所以逐行扫描是绕不开的。我实测扫描一遍完整文件大约十几秒可接受。如果你嫌慢可以先把含kRSUnicode的行 grep 出来存成一个小文件之后脚本只读小文件能快十倍以上。4.2 主脚本一段一段拆开看先看解析部分。这里是整个脚本唯一需要读懂外部格式的地方也是最容易出错的地方# -*- coding: utf-8 -*- import re from collections import Counter UPLUS re.compile(rU\([0-9A-F]{4,6})) RS re.compile(r(\d)?\.(\d)) def parse_unihan(path): 从 Unihan 数据里抽出 字符 - 部首序号 的映射 result {} with open(path, r, encodingutf-8) as f: for line in f: if kRSUnicode not in line: continue parts line.rstrip(\n).split(\t) if len(parts) 3: continue m_cp UPLUS.fullmatch(parts[0].strip()) if not m_cp: continue # 一个字段可能带多个值取第一个主部首 m_rs RS.search(parts[2]) if not m_rs: continue ch chr(int(m_cp.group(1), 16)) result[ch] int(m_rs.group(1)) return result这段代码有三个地方值得单独说。第一用fullmatch而不是match匹配码位避免匹配到奇奇怪怪的前缀。第二parts[2]里可能有多个值用空格分隔用search找第一个数字开头的东西正好把主部首抓出来。第三正则里的?让撇号变成可选这样带不带变体标记都能吃下去。接着是生成码表def build_table(radical_of_char, ch_range(0x4E00, 0x9FFF)): table {} for ch, rad in radical_of_char.items(): cp ord(ch) if not (ch_range[0] cp ch_range[1]): continue initial RADICAL_INITIAL.get(rad) if not initial: continue table[ch] initial return table这里用RADICAL_INITIAL.get()而不是直接下标是为了防御——万一某个字的部首序号落在 1 到 214 之外理论上不该发生但脏数据总有程序不该崩直接跳过就行。这种防御性写法在处理外部数据时是必须的。然后写文件def dump_tsv(table, out_path, sep\t): lines [f{ch}{sep}{code} for ch, code in sorted(table.items())] with open(out_path, w, encodingutf-8, newline\n) as f: f.write(\n.join(lines)) f.write(\n)三个细节encodingutf-8不带 BOM这是输入法导入的必要条件newline\n强制 LF 行尾避免在某个系统上生成 CRLF末尾补一个换行某些解析器对最后一行没换行的文件处理会出问题。这三个都是踩过坑之后加上的不是多余。最后加一段统计输出这个是我最推荐的习惯def report(table): cnt Counter(table.values()) total len(table) print(f生成条数: {total}) for letter, n in sorted(cnt.items(), keylambda x: -x[1]): print(f {letter}: {n:6} 条 {n / total:6.2%}) print(覆盖字母数:, len(cnt))跑完之后你会看到每个字母各占多少条。这个分布本身就是一个质量指标如果某个字母占比超过 15%说明对应的那几个部首撞得太厉害值得回头调整如果有字母一条都没有那说明你的映射表在那个位置写错了。我第一次跑的时候发现 q 特别多查了一下是因为犬欠气青都给了 q这是正常的犬作为反犬旁出现频率极高分布失衡可以接受。完整跑一遍就是if __name__ __main__: raw parse_unihan(Unihan_IRGSources.txt) table build_table(raw) dump_tsv(table, aux_code.txt) report(table)4.3 输出格式对齐这一步千万别想当然格式这一块我必须重点提醒因为你拿到的码表格式和输入法要求的不一定一致。市面上的输入法对自定义辅码表的列定义并不统一有的只认字 辅码两列有的要求字 全码 辅码三列有的干脆要求把辅码接在双拼码后面写成zhang g。如果你直接按自己的理解生成很可能导入之后毫无反应。我的做法是先在输入法的设置界面里找到辅码相关的选项看看有没有导出示例或者查看模板这类按钮。导出一份官方的示例文件用文本编辑器打开数一数列数、看看分隔符、看看有没有表头。拿到这个信息之后回头改dump_tsv那一行就行。
RELATED

相关推荐

生产级智能体沙箱架构:隔离内核选型与安全实践

生产级智能体沙箱架构:隔离内核选型与安全实践

1. 为什么生产环境的智能体必须"关进笼子"1.1 一次让人后背发凉的攻防演练复盘我去年在团队里主导过一个企业内部的智能体平台项目,一开始大家的思路很朴素:把大模型 API 接进来,给它配上搜索引擎、数据库查询、工单系统这几个工具…

📅 2026/10/29 17:02:59
HEMT射频电路设计实战:从选型、偏置到匹配调试的避坑指南

HEMT射频电路设计实战:从选型、偏置到匹配调试的避坑指南

射频电路设计里,晶体管选型这件事,很多时候决定了整个链路的上限。我见过不少项目,前级低噪放指标怎么调都差那么一截,最后发现问题不在匹配网络,而是器件本身在目标频段的噪声特性和增益能力就摆在那儿。高电子迁移率…

📅 2026/10/29 16:57:58
大模型量化从原理到落地:GPTQ与AWQ的显存优化与部署实践

大模型量化从原理到落地:GPTQ与AWQ的显存优化与部署实践

大模型量化这件事,我从最早跑通第一个 GPTQ 权重开始,到现在手上维护着几套不同精度策略的推理服务,踩过的坑比读过的论文还多。很多人第一次接触量化,脑子里冒出来的问题是"精度会不会掉得没法用",但真正上…

📅 2026/10/29 16:57:58
MORE NEWS

更多资讯

📰

context-mode:SQLite+FTS5+BM25构建本地AI上下文检索模式

1. “context-mode”到底是什么?别被术语唬住,它本质是让AI真正“听懂上下文”的工程实践最近在多个技术社区和开发群聊里,“context-mode”这个词突然高频出现,尤其和MCP、SQLite、FTS5、BM25这些词绑在一起。很多人第一反应是&a…

📰

hyperframes:用HTML+CSS实现视频帧级交互的轻量范式

1. “hyperframes”不是新框架,而是对HTML媒体渲染范式的重新命名与实践重构最近在多个前端技术社区和CLI工具讨论区里,“hyperframes”这个词突然高频出现。它既不是某个新发布的JavaScript框架,也不是W3C正在推进的标准草案,更不…

📰

从技能分层到能力模型:打造可落地的技能管理框架

一个只写着“skills”的项目标题,乍看像一个未完成的占位符,但接触过人才盘点、个人知识管理或技术团队搭建的朋友应该都有同感——这个词背后藏着的潜台词其实是:在一个信息过载、工具爆炸的时代,我们到底该如何识别、沉淀、复用…

📰

拆解二维码扫码登录:临时票据、状态机、轮询与安全并发

二维码扫码登录这东西,几乎每个做过 Web 端的人都用过,但真被面试官追着问细节的时候,能从头到尾讲圆的人并不多。我见过不少候选人在这个问题上翻车,不是不知道流程,而是把"扫码登录"当成了一个黑盒&#x…

📰

Superpowers安装与协作开发完全指南:浏览器实时共写游戏

前两天整理开源项目收藏夹,翻出一个躺了大半年的链接:superpowers。这名字挺大气,实际是个基于浏览器的实时协作游戏开发环境。顺手搜了下相关热搜词,“想要安装superpowers”排在很前面,看来最近想入坑的朋友不少。这…

📰

Blender马尾辫制作指南:Ponytail插件从生发到绑定布料模拟全流程

做角色资产的老哥应该都懂:头发永远是进度表上最不可控的那一项,尤其是马尾辫。上个月我接了个角色绑定活,美术要求来一条自然垂坠、甩头时有质感的高马尾。我一开始纯手动:搭低模发片、撒粒子梳理发丝、给整束头发刷权重&#xf…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬