关于 python
面向编码 Agent 的 Python 风格审计技能,结合 ruff 检查与人工判断,检查边界、类型、可靠性、运维、并发和测试。
主要功能
- 01运行 ruff lint 与格式检查
- 02审查边界与数据流
- 03检查公共契约和类型
- 04审计可靠性与运维实践
- 05检查并发与测试实践
适用场景
- 01提交前审计 Python 改动
- 02按代码风格偏好审查代码
- 03建立基础 lint 配置
- 04选择验证与可靠性方案
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ahgraber/ skills --skill python安装后在 Claude Code 中输入/python确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/design-boundaries.mdmulti-python-testing.mdpython-testing.mdscripts/test_pypi_security_audit.pytest_uv_security_audit.py详细介绍
Python 代码风格审计
概述
Python 代码风格审计:与其在编写代码时随身携带规则手册,不如扫描目标(差异、文件、包或仓库),查找违规项并报告。 审计分为两个层级:机械检查交由 ruff 处理,判断性检查则无法用任何代码检查工具表达。 前沿编码 Agent 已能编写合理的 Python,因此以下检查仅限于值得检查的剩余风格偏好。
以下每项默认做法都是推荐的起点。 如果默认做法与项目约束冲突或会导致效果变差,请遵循项目要求,并说明权衡及配套控制措施(测试、可观测性、迁移、回滚)。
何时使用
- Python 变更已完成,即将提交或发布:收尾时审计差异。
- 有人要求审查、审计、快速检查或清理 Python 代码,或确认代码是否符合标准。
- 在可靠性、配置、类型/验证、边界或并发方案之间做选择:对应的第 2 层章节即为风格默认做法。
- 决定 Python 变更需要哪些验证检查。
- 项目没有 lint 策略,需要建立一套策略(第 1 层提供基线)。
何时不使用
- 编写测试时——使用
writing-tests;其中的references/python-testing.md包含 Python 相关细节。 - 处理 Notebook 事件循环机制时——使用
python-notebooks-async。 - 编写演示或教程 Notebook 时——使用
interactive-notebook-demo。 - 需要对差异是否已准备好合并作出结论时——使用
code-review;本技能提供的是 Python 专项审查角度。
调用提示
- 告知用户正在运行此技能:
python。 如果用户按技能名称或斜杠命令请求此技能,则跳过提示;拼写和空格无需完全一致。 本技能触发词列表中的短语不算技能名称——说明工作内容不等于说出技能名称。
通用默认做法
- 使用项目指定的 Python 版本。
- 使用
uv管理环境和依赖;使用uv run ...运行检查。 - 优先使用
#%%.pyNotebook,而非.ipynb,除非明确要求使用.ipynb。 - 遵循本地策略:仓库配置、代码检查规则和项目惯例优先于以下所有内容。
第 1 层 — 机械审计(ruff)
上次验证:2026-08-18 — 对照 https://docs.astral.sh/ruff/default-rules/ 和 ruff 更新日志,重新验证默认规则覆盖范围和版本说明。
首先使用项目自身的配置运行:
uv run ruff check .
uv run ruff format --check .
- ruff >= 0.16 启用了广泛的默认规则集(413 条规则):bugbear、裸
except、try/except/pass、不带时区的日期时间、异步代码中的阻塞调用和缺失的超时、悬空的asyncio.create_task引用、未使用的异步代码,以及日志调用误用,开箱即由机器检查。 绝不要手动审计 ruff 已检查的内容;运行它并按规则 ID 引用检查结果。 - 如果项目固定使用较旧版本的 ruff 或限制了
select,请遵循本地策略;只有当与本次审查的变更实质相关时,才说明其与风格基线之间的差距。 - 如果项目没有自己的 lint 策略,请提议以下起点,而不是强行推行:
[tool.ruff.lint]
extend-select = [
"ANN2", # missing return types on public functions
"ASYNC", # async hygiene beyond the default subset
"DTZ", # full naive-datetime coverage
"S113", # requests calls without timeout
"T20", # stray print in library/service code
"TRY", # exception design (raise/except hygiene)
]
ignore = ["TRY003"] # long messages in raise — noisy in practice
- ruff >= 0.16 还会格式化 Markdown 中的 Python 代码块,并支持
ruff: ignore[rule-name]抑制注释;新增抑制时优先使用这些注释,而非单独使用noqa代码。 - 对于依赖或锁文件变更,请从目标项目的根目录运行供应链审计脚本,并通过本技能
scripts/目录中每个脚本的完整路径传入:uv run pytest <path-to-this-skill>/scripts/test_uv_security_audit.py <path-to-this-skill>/scripts/test_pypi_security_audit.py -v。 两个脚本发现问题时会发出警告(而非失败);请阅读警告。
第 2 层 — 判断性审计
针对以下每类问题扫描目标。 这些问题无法通过机器检查;报告每个问题时,注明文件名和行号,并给出具体修复方案。 每项都会说明要查找什么、问题所在,以及可解决问题的风格默认做法。
边界与数据流
在为此类问题提出修复方案之前,请阅读 references/design-boundaries.md,了解这些默认做法背后的原因以及更全面的修复指导。
- 查找核心业务逻辑中的文件 I/O、环境读取,或时钟与随机数调用。 隐藏在核心中的副作用会让逻辑无法在没有模拟对象的情况下测试,并使决策与基础设施耦合。 默认采用函数式核心/命令式外壳:核心根据输入计算决策,外壳执行副作用。
- 查找未经验证就进入领域逻辑的外部输入。 在调用栈深处才进行检查时,错误数据早已扩散。 在入口处验证并规范化数据,让边界之后的所有代码都可以信任输入。
- 查找跨模块边界传递的 ORM 模型、提供方 SDK 类型或持久化架构。 跨越边界的任何内容都会成为契约,而内部类型会连同其整个架构一起带过去。 改用精简的 DTO 或值对象。
- 查找跨模块边界的事务。 共享事务会让模块的故障模式相互耦合,并带来无人负责的回滚复杂性。 默认通过事件或作业协调跨模块工作,最终保持一致。
- 查找臆测性的抽象、深层继承,以及
utils/common代码堆放区。 在第二个具体用途出现之前就建立的抽象通常并不合适,而 utils 模块里的代码则无人负责。 构建最精简的黄金路径,并优先采用组合。
契约与类型
在为此类问题提出修复方案之前,请阅读 references/design-boundaries.md,了解这些默认做法背后的原因以及更全面的修复指导。
- 查找未注明类型或类型为
Any的公共函数。 公共签名就是契约;没有类型时,调用方只能猜测,重构也会失去安全保障。 内部辅助函数无需达到同样严格的要求。 - 查找类型声明比函数实际所需更具体的参数,以及类型声明比函数实际返回内容更宽泛的返回值。
接受抽象接口(
Mapping、Sequence、Protocol),避免强迫调用方使用特定容器类型;返回具体类型,让调用方清楚自己拿到的是什么。 - 查找贯穿内部数据流的 pydantic 模型。
验证属于边界处理;到处使用模型会增加开销,并使内部代码与架构层耦合。
将 pydantic 限定在可信边界——API 入口、配置和外部数据——并在新代码中使用 v2 API(
model_validate、model_dump)。 - 查找没有弃用说明、版本管理或迁移说明的公共契约变更。 下游使用者会在不知情的情况下受到影响。 优先采用增量式变更;有意进行的破坏性变更应附带迁移计划。
可靠性策略
- 查找未设置显式超时的出站 I/O,以及没有截止时间的长时间运行操作。
库的默认值可能不受限制或随意设定,因此发生故障时的延迟会变得不可预测。
为所有外部 I/O 设置显式超时,并将绝对截止时间(
time.monotonic() + budget)传递给嵌套调用,而不是在每一层重新计算相对超时时间。 - 查找不受限制、在多个层级叠加,或用于永久性故障的重试。
不受限制的重试会把一次故障变成重试风暴;多层重试会让尝试次数成倍增加;重试验证错误只会拖延真正的修复。
限定重试次数和总截止时间,采用带抖动的指数退避,遵循
Retry-After,并确保重试逻辑只存在于一个层级。 - 查找没有幂等性保障的重试写入。 故障情况不明时进行重试,可能导致写入被应用两次。 使用幂等键或去重令牌,或者禁用该操作的自动重试。
- 查找通过匹配异常消息字符串来判断是否可重试的情况。
消息文本不是契约,可能在没有预警的情况下发生变化。
将是否可重试编码为策略数据——错误类型和
retryable标志,并为可重试和永久性结果设置不同的终态。 - 查找只报告笼统成功或失败的批量操作。 调用方无法判断哪些项目已成功,因此唯一安全的恢复方式就是重新运行全部操作。 除非实际契约要求全部成功或全部失败,否则应返回每项操作的结果。
运行时与运维
- 查找在使用时才读取配置的情况。
缺失或格式错误的值可能要等到运行几分钟甚至几小时后才暴露,而不是在启动时发现。
加载有类型的设置(例如
pydantic-settings),在启动时验证,并确保导入过程不会产生配置副作用。 - 查找缺少信号处理、有界排空或
try/finally清理的关闭路径。 未处理的 SIGTERM 会使子进程成为孤儿,并留下写入一半的状态;不受限制的排空会导致重启时进程挂起。 处理终止信号,停止接收新任务,在截止时间内排空正在处理的工作,并在try/finally中清理子进程、临时文件和句柄。 - 查找终态不明确的任务。 重试耗尽后仍表现为待处理工作,会让故障无法被发现,队列也会逐渐堆积。 将可重试和永久性结果记录为不同状态,并明确配置死信策略。
- 查找日志记录无结构化,或每次故障触发多条日志的情况。 重复的自由文本日志无法计数、筛选或关联。 在入口点配置一次日志记录,使用模块级日志记录器,在有意义的边界对每次故障只记录一次,并包含操作 ID、阶段和尝试次数,同时避免记录机密。
- 查找未检查依赖项就返回 OK 的健康或就绪探针。 对依赖探针的编排器而言,虚报健康状态会掩盖级联故障。
并发
- 查找与工作负载不匹配的并发模型。 用线程处理 I/O 扇出会增加同步错误,而 asyncio 可以避免这些问题;asyncio 对 CPU 密集型工作没有帮助,这类工作需要使用进程。 根据工作负载选择模型,只有理由明确时才使用线程。
- 查找不受限制的扇出:对不受限制的集合使用
gather或submit。 成千上万个并发任务会使事件循环资源紧张,或耗尽内存和文件句柄。 使用信号量或队列限制并发量。 - 查找取消操作只停止父任务、却没有停止子任务或跳过清理的情况。 孤儿任务会一直占用连接并泄漏资源,直到进程退出。 将取消传播到子任务,并等待其完成清理后再重新抛出异常。
- 查找没有前后测量的性能优化工作。 未经性能分析的优化会解决错误的问题,而其复杂性却会保留下来。 先测量,再重新测量以确认效果。
测试
Python 专属测试实践见 references/python-testing.md。
- 查找有多个写入路径、但只测试了规范路径的契约断言值。 去重捷径或缓存快速路径可能会悄无声息地丢弃契约要求的内容。 每个写入位置都需要单独的测试,派生字段的不变量则需要针对组合后的输出进行测试。
- 查找以模拟调用次数断言代替行为断言、在定义位置而非导入位置应用补丁,以及使用裸
Mock()(而自动规格可捕获签名偏差)的情况。 这些情况都会导致测试通过,但行为却是错误的。 - 查找已合入但没有先失败过的回归测试的错误修复。 一个从未失败过的测试无法证明错误已修复。
报告
- 运行命令并引用输出,报告 tier 1;报告 tier 2 问题时,注明文件:行号、违反的默认规范及建议的修复方案。
- 区分“违反团队默认规范”和“违反本项目自身惯例”;后者优先级更高。
- 仅在用户要求时或任务本身就是清理工作时修复问题;否则仅报告。
参考资料
references/design-boundaries.md— Functional Core / Imperative Shell、模块归属、边界契约、pydantic 的放置位置、契约演进references/python-testing.md— Python 专属测试实践(也已共享给writing-tests)references/multi-python-testing.md— 使用 nox + uv 进行版本矩阵测试