尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
构建个人技能树:Python命令行工具设计与实现
年初整理简历时我发现一个特别尴尬的问题简历上写了七八项技能可真要我回答“你这项技能到底到了什么程度”“它依赖哪些前置知识”“下一步该补什么”脑子里的答案全是“大概会一点”“用过一阵子”。这种模糊在面试里很容易露馅更麻烦的是它让我自己的学习规划也变得一团糟。我试过用 Excel 拉大表但每次更新到一半就放弃了。后来我干脆拆了拆需求动手写了一个叫 skills 的命令行工具用来管理个人技能树、记录学习进度、解析技能依赖、生成学习路径和 HTML 报告。这篇文章就是把整个项目的思路、技术选型、核心代码和踩过的坑都摊开来讲适合那些也想自己动手做一套“技能管理系统”的开发者、自由职业者或者正在转行、想理清学习路线的新人。1. 从“我到底会什么”到 skills 项目1.1 为什么技能管理不能靠一张 Excel先把最真实的痛点摆出来。我以前觉得“技能清单”就是一张表名称、分类、年限、熟练度顶多加一列备注。可真正用起来Excel 的局限非常明显。技能不是孤立存在的它天生带依赖关系。比如你写“精通 FastAPI”实际上隐含了 Python、HTTP 协议、异步编程、数据库操作这些前置技能你写“会用 Docker”背后是 Linux 基础、Shell 命令、网络端口、镜像与容器原理。这些关系在表格里根本表达不出来只能平铺成一行行看似整齐、实则混乱的字符串。另一个问题是熟练度全靠感觉。我刚开始给自己打分的时候“Python 熟练度 4/5”可实际上一整个月都没写过正经脚本。分数一旦脱离行为数据就只是自我安慰。这也是很多学习类打卡工具最后沦为摆设的原因记的是“我以为自己会什么”而不是“我实际做了什么”。我需要的不是一张越拉越长的表而是一个能记录过程、自动推算差距、告诉我“先学什么后学什么”的系统。于是核心需求就出来了定义技能节点和依赖边记录每次实践事件根据依赖关系计算学习路径最后生成一份可以分享的 HTML 报告。这也正好对应了我给自己定的规则——技能不是“我掌握了它”而是“它和我当前的过程数据共同证明我掌握了它”。1.2 功能边界先解决自己的问题再谈通用很多人一写工具就想做一个“人人都能用”的产品。我的建议恰恰相反第一版只解决你自己的问题能跑起来比架构优雅重要得多。所以 skills 最初的功能清单刻意很短用 YAML 文件定义技能清单包括名称、分类、熟练度、依赖项。用命令行快速记录学习事件比如今天完成了什么练习、读完了哪份文档。能根据技能之间的依赖关系计算从“当前已掌握”到“目标技能”的学习路径。一键生成静态 HTML 报告方便回顾和分享。用户系统、在线同步、多人协作、移动端这些统统没做。不是不想做而是个人工具的边界一旦扩大开发周期会无限拉长最后大概率烂尾。把边界画清楚两周内跑起来有了真实数据之后再慢慢迭代才是这种小项目的正确姿势。2. 技术选型与架构决策2.1 为什么用 Python 而不是 Node 或 Go选型这事儿我第一反应不是“哪个语言流行”而是“哪个语言能最快解决我的核心问题”。我需要三件事解析 YAML、构建有向图、输出 HTML。Python 在这三方面几乎都有现成答案。YAML 有 PyYAML图论有 NetworkXHTML 模板可以用 Jinja2再加上 Typer 写命令行参数Rich 打印彩色表格整个开发体验非常顺畅。对比一下其他方案Node 生态也有 js-yaml 和 graphology但图算法工具链没有 NetworkX 顺手Go 编译成单文件分发很爽可写代码节奏明显比 Python 慢而且涉及 YAML 反射这类操作时心智负担会增加。Python 最大的缺点是打包分发需要虚拟环境但对于个人工具来说完全不是问题反正只有我自己用.venv一建pip 一装完事。还有个隐性原因是数据处理。我后期想做熟练度趋势分析Python 的 Pandas 或者纯标准库都能轻松处理 SQLite 里的时间序列数据。这属于“给未来留后路”但不是过度设计因为在写第一版的时候“每项技能最近有没有活动记录”这个统计需求就已经能看到了。2.2 数据存储YAML 做人的接口SQLite 做机器的接口skills 用了两套存储理由完全不同。技能定义放在 YAML 文件里路径是data/skills.yaml。为什么不用数据库存配置因为技能定义是“人写给人看”的用 YAML 可以写注释、可以 git diff、可以随时手工编辑。比如我要临时给某技能加一个 tag直接打开文件改一行就行。配置示例skills: - name: Python category: language level: 4 deps: [] tags: [backend, script] - name: FastAPI category: framework level: 3 deps: [Python, HTTP] tags: [api] - name: Docker category: devops level: 2 deps: [Linux, Shell] tags: [container]这里level是自评分数0 到 5后面会结合行为数据做调整。deps是技能名称列表表示“我想掌握这项必须至少同时掌握这些前置技能”。注意技能名称必须全局唯一因为它是图节点的 ID。学习进度放在 SQLite 里路径是data/progress.db。为什么进度不用 YAML因为进度是不断追加的“时间序列事件”用文本文件追加 Append 操作很别扭并发写入更是麻烦。SQLite 零部署、单文件、支持 SQL对个人工具来说恰到好处。核心表就一张CREATE TABLE progress ( id INTEGER PRIMARY KEY AUTOINCREMENT, skill TEXT NOT NULL, event_type TEXT NOT NULL, note TEXT, created_at TEXT DEFAULT (datetime(now)) );event_type我限制为三种practice代表练习、project代表真实项目、read代表阅读文档/课程。区分类型是为了报告里能看到“这项技能的熟练度到底靠什么撑起来的”。2.3 模块划分一个命令入口四个独立模块整个项目的结构尽量保持简单。我把它拆成了五个文件谁也不许偷偷膨胀skills/ ├── data/ │ ├── skills.yaml │ └── progress.db ├── skills/ │ ├── cli.py │ ├── model.py │ ├── loader.py │ ├── graph.py │ ├── progress.py │ └── render.py └── templates/ └── report.html.j2职责划分很明确cli.py只负责解析命令和参数把用户输入转发给下层model.py定义技能数据类loader.py读 YAMLgraph.py负责建图、找依赖、算路径progress.py管理 SQLite 的读写render.py负责把数据灌进 Jinja2 模板。这样哪怕后续增加新命令也不需要重构全局只要在某一个模块里加函数就行。3. 核心实现数据模型、依赖解析与报告生成3.1 技能树的数据结构让依赖关系变成有向图我最初想用嵌套字典表示技能树类似{语言: {Python: {...}}}。可嵌套 JSON 没法表达“FastAPI 同时依赖 Python 和 HTTP而 HTTP 又依赖网络基础”这种交叉依赖。二维树结构解决不了必须升维成图。所以核心数据结构是 NetworkX 的DiGraph有向图。每个技能是一个节点每个“依赖”是一条边方向从“前置技能”指向“后置技能”。比如 FastAPI 依赖 Python就建一条Python - FastAPI的边。这样“学习顺序”天然对应图上的拓扑序想学后置节点必须先把它前面的节点走完。import networkx as nx from skills.loader import load_skills g nx.DiGraph() def build_graph(): skills_data load_skills(data/skills.yaml) g.clear() for s in skills_data: g.add_node(s[name], **s) for dep in s.get(deps, []): if dep not in g: # 允许依赖项先出现在图上后面再补全节点属性 g.add_node(dep, namedep, level0, deps[]) g.add_edge(dep, s[name]) return g这里有一个细节某些依赖技能可能不在 YAML 的显式列表里。为了不让程序崩溃我选择“自动创建占位节点”level 默认 0表示“还没被我定义过”。在报告里看到 level 0 的技能我就会意识到哦原来我在 FastAPI 里暗含了一个叫 HTTP 的技能但从来没正式登记它。这种“隐性技能显性化”正是这个工具最有用的地方。3.2 熟练度评估自评、行为数据与依赖折扣“熟练度”是最容易自欺欺人的字段。我第一版直接用 YAML 里的level结果发现没用因为我给自己打分会手软。后来我改成三个层面的数据综合第一层是自评分依然用 0 到 5但只作为基线。第二层是行为修正查询 SQLite 里该技能的事件数量、最近一次事件距今的天数。如果 30 天内有practice或project记录基础分加 0.5如果 90 天内没有记录基础分减 0.5。第三层是依赖折扣计算所有未掌握依赖的平均分差如果一门技能的前置技能里有明显短板这门技能的实际得分最多再往下打八折。简化代码import datetime def adjusted_level(skill_name, base_level, progress_count, last_used_days, dep_scores): score float(base_level) if progress_count 3: score 0.5 if last_used_days 90: score - 0.5 missing_deps [d for d, ds in dep_scores.items() if ds 1.5] if missing_deps: score * 0.8 return max(0.0, min(5.0, score))这个计算方式并不完美但它的价值在于“可解释”。每当报告里出现一个不符合直觉的分数我都能从行为数据里找到原因。比如“Docker 为什么只有 1.8”因为我 4 个月没碰它了而且 Linux 基础分太低。与其说它是精确度量不如说它是一个逼迫你面对现实的镜子。3.3 CLI 命令设计与输出示例CLI 用的是 Typer写起来和 argparse 相比更简洁而且天然支持类型提示、自动补全帮助。最常用的命令如下skills init skills add --name FastAPI --category framework --level 3 --deps Python,HTTP skills log --skill FastAPI --type practice --note 写完 CRUD 接口 skills list --category framework skills path --target FastAPI skills report --output skills.htmladd命令会做循环依赖检测log命令负责往 SQLite 里记一条事件path是重头戏它输出的不是简单列表而是一段按依赖优先级排序的学习路径目标: FastAPI 当前缺失技能: 1. HTTP (建议先学) 2. Python 进阶 (建议先学) 推荐路径: HTTP - Python 进阶 - FastAPI这个路径怎么来其实就是在有向图上找从“当前已掌握集合”到“目标节点”的所有缺失祖先节点然后用拓扑排序输出。拓扑排序保证每个技能出现在它的依赖之后也就是“先修基础、再学进阶”的自动实现。4. 实操过程从 0 到 1 搭建 skills4.1 搭建项目骨架与安装依赖动手之前先把隔离环境建好。我习惯用 Python 自带的venv不装 Poetry 这类重武器个人工具够用就行mkdir skills cd skills python -m venv .venv source .venv/bin/activate pip install typer rich networkx pyyaml jinja2然后创建上面的目录结构。需要特别提醒的是data/目录最好从一开始就纳入 git 管理因为skills.yaml是你的核心配置progress.db虽然是二进制但我建议也提交到私有仓库这样换电脑或者出问题的时候能追溯过程数据。当然如果担心隐私可以加.gitignore忽略 db 文件只保留 YAML。默认情况下我两个都提交。4.2 依赖解析与学习路径规划的实现path命令的核心代码在graph.py里我一开始想得特别简单找出目标技能的所有祖先节点减去自己已经掌握的剩下的按顺序输出。可真动手时遇到了一个问题祖先节点之间也有依赖不能随便排序。你需要用拓扑排序来保证“先底层后上层”。import networkx as nx def find_learning_path(g, current_skills, target): if target not in g: raise ValueError(f目标技能 {target} 不存在) all_ancestors nx.ancestors(g, target) missing all_ancestors - current_skills - {target} if not missing: return [target] subgraph g.subgraph(missing | {target}) try: order list(nx.topological_sort(subgraph)) except nx.NetworkXUnfeasible: raise ValueError(技能依赖存在循环请检查 skills.yaml) path [node for node in order if node in missing] [target] return pathnx.ancestors返回目标节点所有上游节点。比如 FastAPI 的上游包括 Python、HTTP、TCP/IP、网络基础等。减去current_skills我在 CLI 里通过--have参数或者默认从 YAML 里筛选 level 3 的技能作为“已掌握”剩下的就是需要补的课。拓扑排序输出的顺序就是推荐学习顺序。这个实现有一个明显好处如果以后想把“学习路径”扩展成“备选路径”比如“可以学 HTTP 或者先学网络基础二者都行”只需要在报告阶段对同等深度的节点做分组展示不需要动图算法部分。4.3 渲染 HTML 报告的关键细节报告是我最看重的功能因为如果数据只是存在数据库里没有任何人看那么再完美的算法也是自我感动。我用 Jinja2 做模板数据从 Python 里传入后生成一个独立 HTML 文件不依赖任何网络资源可以放到任何地方打开。模板核心片段大概是这样的h2{{ skill.name }}/h2 span classbadge{{ skill.category }}/span span classlevel{{ skill.level }}/5/span {% if skill.deps %} p依赖: {{ skill.deps | join(, ) }}/p {% endif %} p最近活动: {{ skill.last_used }}/p在render.py里我构造一个report_data字典包含技能列表、分类汇总、依赖图 JSON 等然后一次性渲染。为了后续画雷达图我还会输出一份纯 JSON 数据方便浏览器里的脚本读取{ categories: [语言, 框架, 运维], series: [ {name: 你当前的熟练度, data: [4.0, 2.8, 1.5]} ] }HTML 报告里最醒目的部分不是数值列表而是一句话“你最近 30 天内没有给任何技能记录过实践事件。”这句话比所有算法都诚实。我故意把它放在页首这样每看一次报告都是一次提醒。5. 踩坑实录与常见问题排查5.1 依赖循环引用差点把自己绕进去第一版跑通后我给技能列表加了个“HTTP”节点手滑把依赖写反了变成了“Python 依赖 FastAPI 且 FastAPI 依赖 Python”。结果执行skills path --target FastAPI时直接抛异常。NetworkX 的topological_sort在有向有环图上是无解的异常是NetworkXUnfeasible。这其实是一个很棒的故障信号。我正在add命令里加了检测逻辑每次写入 YAML 之前先构建全量图用nx.find_cycle检查是否有环。如果有环拒绝写入并告诉你“循环路径Python - FastAPI - Python”。与其等路径规划时崩不如在源头拦截。这种“配置即数据”的工具最好把所有校验都放在写入端。5.2 YAML 类型转换与中文路径YAML 有一个非常容易踩的坑它会把2024-01-01自动转成日期对象而不是字符串。如果技能名称叫2024-01-01这种几乎不会或者note字段里写了日期格式的字符串都会被隐式转换。我的方案是统一给技能名称和描述加引号或者配置 YAML 加载器时用yaml.safe_load且避免裸日期。最稳妥的还是在定义里显式写成2024-01-01。中文路径的问题主要出现在 Windows 上。Python 的open()在某些编码环境下打开含中文的路径会抛 UnicodeEncodeError。解决方式是所有文件读写都显式指定encodingutf-8。同时skills add --deps参数里如果技能名包含英文逗号或中文逗号解析会出错。我后来统一用空格分隔或者要求传 JSON 数组虽然笨但至少不会莫名其妙丢数据。5.3 进度数据缺失与“虚假熟练度”工具用了两个星期后我发现一份报告显示“FastAPI 熟练度 5.0”但事实是我只写了三个练习接口根本没有交付真实项目。为什么会虚高因为我的算法里只要有 3 条 progress 记录就加 0.5 分而我自己给自己打的基础分是 4.5。两个加成叠在一起直接顶满。这是一个典型的“算法被游戏化”案例。我没有去改算法而是在报告里增加了一个“数据可信度”字段。计算方式是熟练度得分里行为修正和依赖折扣带来的比例。如果大部分权重是“自评”就会显示“可信度低请补充更多实践记录”。这个设计不是为了打分而是为了揭示“你自我感觉良好但数据不支持”的情况。诚实比精确更重要。5.4 SQLite 写入锁问题我习惯用 cron 在每天下班前自动执行skills log --skill daily --type practice --note 自动记录来强制打卡。结果某天手动执行命令时偶尔会遇到database is locked。原因很简单cron 进程和手动命令同时写 SQLite互相争锁。解决方案是启用 WAL 模式。在初始化数据库时执行一次PRAGMA journal_modeWAL;读和写就能并行不再频繁锁库。再配一个busy_timeoutPRAGMA journal_modeWAL; PRAGMA busy_timeout3000;从此再没遇到过锁库报错。这个坑让我意识到个人工具也要考虑“自动化任务和手动操作并发”的场景否则日后的自动化步骤越多碰到的怪问题就越多。6. 从 skills 到个人知识管理的一些体会6.1 工具不是关键习惯才是skills 真正改变我的不是那几条命令而是我把“记录”变成了一种习惯。过去学完一个东西就扔到脑后现在会顺手敲一句skills log --skill xxx --type practice --note ...。为了让这个动作足够轻我给终端配了一个 aliassklskills log。打完收工的时候脑子里想的是“今天给哪个技能投资了”而不是“又要维护表格了”。另一个小技巧是让报告自动产生。我在项目的.git/hooks/pre-commit里挂了一个脚本每次 git commit 之前自动运行skills report --output docs/skills.html。这样报告永远和代码一起更新我根本不需要记着去生成。6.2 后续可以扩展的方向skills 目前的版本已经够我用但我在笔记里也列了一些未来可能的方向按优先级排序对接日历或 Todo 系统自动从任务数据里识别技能相关实践。增加“目标职位”模块输入目标岗位的技能要求自动生成与现状的差距清单。生成 PDF 版技能页直接导出成简历附件。引入趋势曲线把 SQLite 里的时间序列画成折线图直观看到每项技能的活跃周期。做成一个局域网 Web 服务朋友之间互相查看技能结构给自己一点社会压力。这些扩展都有一个前提基础数据模型稳定。目前我故意给每个技能加了meta字典字段以后哪怕加“语言级别”或“证书链接”也不用改 YAML 解析逻辑只需要读取meta里对应的键名。6.3 想自己造轮子的人我的一句话建议如果你也想做类似的个人工具我的建议是第一版宁可糙不能拖。先实现init、add、list、report这四条命令剩下的全部砍掉。数据结构不要一开始就想搞插件化、动态表单、在线同步就一个 YAML 文件一个 SQLite 数据库足够了。等你连续用它记录了半个月真实数据之后再回头看哪些功能真的缺、哪些纯属自嗨。我最近复盘时发现过去几个月 skill 视图里最明显的改变不是某个分数涨了 0.5而是“知道自己不知道什么”这件事变得异常清晰。以前我总想着“FastAPI 应该要学一下”现在会自动变成“先补 HTTP再动 FastAPI”。这就是把隐性依赖显性化的力量。如果你也在整理自己的技能栈建议先别急着抄任何人的技能清单而是用自己的坐标体系定义一遍然后选择一个顺手的工具把过程记录下来。坚持一段时间之后你会发现那个只能add和list的小东西已经悄悄改变了你的学习节奏。
RELATED

相关推荐

Redis缓存穿透击穿雪崩实战:故障链路与有效方案

Redis缓存穿透击穿雪崩实战:故障链路与有效方案

Redis缓存穿透、缓存击穿、缓存雪崩,这三个词在我看来是后端面试的标配,也是线上事故的常客。我见过太多能把三者的定义倒背如流的人,但真到告警弹出的那一刻还是会认错问题——以为是穿透,查了半天发现是击穿;雪崩已经…

📅 2026/10/21 5:00:56
三维重建的步骤-1(采集的是环绕物体均匀旋转一圈的视频)

三维重建的步骤-1(采集的是环绕物体均匀旋转一圈的视频)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/21 5:00:56
以太网高可用方案:网络架构、IP规划与Keepalived VIP漂移实录

以太网高可用方案:网络架构、IP规划与Keepalived VIP漂移实录

前几天还有个朋友问我,说他们内网系统要做高可用,是不是必须得上一台硬件负载均衡设备?我跟他讲,很多时候不用那么折腾,传统的Ethernet以太网加上合理的IP规划和VIP(虚拟IP)方案,就能…

📅 2026/10/21 5:00:56
MORE NEWS

更多资讯

📰

sentry_drift鸿蒙化适配:Flutter数据库级性能追踪

不是标题起得唬人,是这个问题确实绕不开。我最近在一个模拟的 Flutter 跨端项目里做性能治理,页面滑动掉帧、接口返回慢这些常规问题排查完之后,发现一个更隐蔽的瓶颈——数据库层面的慢查询。SQLite 操作本身可能只有几十毫秒,但…

📰

VS Code 高效开发实战:从安装配置到插件调试完整指南

这份学习笔记,来自我在多个项目中从“只会打开文件写代码”到“能把一整套开发流程顺畅跑通”的真实积累。VS Code 的强大不在于它默认带了多少功能,而在于它把编辑器、终端、调试器、版本控制这些高频操作,用一套可配置的框架融合在了一起。…

📰

AI调度官真的能管好团队?从拆目标到团建的三周实测拆解

我一直对“让智能体管团队”这件事嗤之以鼻。市面上的AI工具我试过不少:有的能生成任务清单,有的会自动回复消息,可真让它去拆周目标、分派活、催进度、协调资源,它基本就成了一个“高级复读机”。开会、排期、团建这些事&#xf…

📰

智能体技能体系设计:从提示词到可复用能力单元

1. 从"技能"这个词说起:为什么智能体需要一套技能体系第一次看到"agent-skills"这个命名,我脑子里冒出来的第一个念头是:这不就是把智能体当成一个"新员工"来培养吗?你招一个人进来,他光…

📰

GPT-4o API接入实战:认证、流式输出与工程优化全攻略

2. 调用核心机制与参数配置其实说“解锁”,对于大部分开发者来说,真正要解决的是两件事:第一,怎么拿到合法的调用凭证;第二,怎么把请求封装得又快又稳。这一节我把这两块展开讲透。2.1 认证机制与请求流程G…

📰

CactiEZ实战:华为、H3C、中兴交换机SNMP监控模板导入与排错

简介:面向CactiEZ 10.1监控平台用户,这份资源提供了一套开箱即用的主机监控模板包,专门解决网络运维人员在纳管华为、中兴、H3C等设备时手动编写SNMP模板的麻烦,也适用于需要同时监控UPS、打印机等辅助设备的机房场景。包内共16个…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

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

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

📞 💬