Communitygithub.com

mathias-mrsn/skills

审查并简化 Python 代码设计。

skills 是什么?

该技能旨在审查和重构 Python 代码设计,使其更简洁、易读和可维护。它基于 Bruce Eckel 的《Thinking in Python》等理念,强调用最精简的构造解决问题,避免过度设计。技能提供了详细的规则,涵盖语法、命名、引用与可变性、数字与真值、字符串与插值、抽象与依赖等各个方面。支持任何 Python 项目,不指定部署环境或框架。通过识别代码中的问题并提出改进建议,该技能帮助开发者编写更符合 Python 习惯的代码。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mathias-mrsn/skills/tree/HEAD/skills/technical/python/python-code-quality

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Python 代码质量与设计

适用场景

  • 编写或审查虽然能工作但难以理解、扩展或维护的 Python 代码。
  • 将从 Java 或 C++ 直接翻译过来的代码替换为更适合 Python 语言的构造。
  • 选择抽象层次、评估耦合度或在新需求出现后进行重构。
  • 识别设计模式的意图,但不要将其名称变成架构上的强制义务。
  • 本范围关注代码本身的推理,既不规定部署环境也不规定框架。

理念

寻找能够解决当前问题的最小构造,而不是想象中最通用的设计。 当 Python 已经提供了某些结构时,保留设计模式的意图意味着去除重复结构是一种改进,而不是缺乏 sophistication。 好的抽象能在不擦除客户端所需信息的前提下降低变更成本。 基于 Bruce Eckel 的《Thinking in Python》以及语料库 a8feb860 的操作性综合。 尊重项目实际支持的 Python 版本:语料库近期目标版本不构成隐式版本升级的依据。

规则

可读的语法和可预测的命名

  • 让缩进匹配实际的块结构;使用四个空格,不要混用制表符和空格。
  • 块头部以冒号结束;避免使用分号和人为压缩代码的多条语句。
  • 对于长语句,使用圆括号、方括号或花括号内的续行,而不是难以跟随的分隔。
  • 不要混淆块与作用域:if 或 for 中的赋值可能在块之后仍然可见。
  • 确保在使用名称前的每条路径上都执行了赋值;仅靠缩进不能保证它的绑定。
  • 对变量、函数、方法和文件使用 snake_case;类使用 PascalCase;全大写表示常量。
  • 保留将类命名为函数风格的例外,仅用于实际上这样使用的对象;不要将其作为通用风格。
  • 不要从大写名称推断运行时保护:它向读者传递一种意图。
  • 保留项目一致的惯例并使用其已有的 linter,而不是创建任意的局部规则。

引用、值和可变性

  • 将赋值理解为将名称绑定到对象;它不会复制对象。
  • 明确区分重新赋值与修改:重新赋值名称会保留其之前的别名不变,而修改对象则可能影响所有别名。
  • 对值相等使用 ==;将 is 保留用于有意的身份比较,特别是 is None 和其他单例。
  • 不要使用字符串或小整数的身份作为值测试:共享优化不提供该契约。
  • 列表的切片或浅拷贝会重新创建外层结构,但不会重新创建嵌套元素;检查哪些对象仍然共享。
  • 仅在需要复制所包含对象时才使用深拷贝;赋值无法提供这种隔离。
  • 不要机械地将 x += y 替换为 x = x + y:对于列表,前者原地修改,后者生成新列表。
  • 对于不可变数字,理解增强赋值是将名称重新绑定到结果,而不是修改共享的数字。
  • 在不需演进的情况下优先使用不可变数据,但不要混淆容器的不可变性与内容的不变性。

数字与真值

  • 区分 /(整数操作数时产生浮点数)与 //(向负无穷舍入的整数除法)。
  • 在移植时测试负数情况:整数商不是向零截断,余数 % 的符号遵循除数。
  • 不要假设 Python 整数具有固定宽度;它们不会像其他语言中的有界整数那样溢出。
  • 将精确整数运算与浮点运算分开;不要将一个领域的保证引入另一个领域。
  • 在 round() 和格式化时考虑偶舍入(银行家舍入);不要假设总是远离零舍入。
  • 在字面量中使用下划线分隔符使大数可读,且不影响其值。
  • 使用 += 1 和 -= 1 进行增减;Python 没有 ++ 或 -- 运算符。
  • 布尔值也是整数:对条件求和可以计数真值情况,但 True 并非独立于 int 的数字类型。
  • 当问题仅关乎容器是否为空时,优先使用 if items: 而非长度测试。
  • 记住对象的真值可由 __bool__() 定义,否则由 __len__() 定义。
  • and 和 or 会短路并返回一个操作数,不一定是布尔值;不要仅根据名称来注释其结果。
  • 仅当所有假值都应被替换时才使用 x or default;如需保留零或空文本,用显式的 None 测试。
  • 对于掩码,区分位运算符与逻辑运算符;~x 不会反转一个固定宽度的字。
  • 不要期望 @ 对内置数字执行乘积运算:矩阵乘法取决于类型,与装饰器语法不同。

字符串与插值

  • 字符串不可变:如果需要重用转换结果,请保留它。
  • 选择限制转义的引号;当换行符是文本一部分时使用三引号。
  • 对字面反斜杠使用原始字符串,注意此类字面量不能以单独反斜杠结尾。
  • 使用内置的搜索、分割、替换和连接操作,除非有特殊需要,否则避免字符操作循环。
  • 不要使用 strip、lstrip 或 rstrip 来移除精确前缀:它们的参数表示字符集合,而非不可分的子串。
  • 如需移除精确的前导或尾随子串,在目标版本支持时使用 removeprefix() 和 removesuffix()。
  • + 不会隐式地将数字转换为字符串;请使用显式转换或格式化字符串。
  • 对于生成普通文本,优先使用 f-string;其中的表达式会被求值并立即渲染结果。
  • 使用格式规范来指定精度、宽度和对齐;!r 显示对诊断有用的表示,= 标记表达式。
  • 不要仅仅因为旧版 % 或 str.format() 存在就重写它们:它们仍然有效,迁移应旨在改善相关代码。
  • 仅当目标版本支持且消费者需要在组装前区分字面量和值时,才考虑使用模板字符串(t-string)。
  • t-string 生成结构化对象而非最终字符串;消费者必须根据其领域实际地引用、转义或拒绝插值。
  • 不要仅基于插值语法来承诺安全性,也不要将说明性的字符拒绝重新用作通用保护。
  • 模板消费者不能假设文本与值严格交替:迭代可能省略空字面量。

抽象、依赖与内聚

  • 在添加新类之前,检查函数、字典、模块或语言协议是否足够。
  • 仅因托管一个方法而存在的类不能凭这种组织方式证明其合理性。
  • 根据实际需求命名变更轴;同一位置的重复变更是比预期灵活性更有用的证据。
  • 将变化的部分与稳定的部分分开;在没有变化时不要构建变化机制。
  • 将惯用法、具体方案和通用模式视为可能的阶段,而不是必须攀爬的阶梯。
  • 对每个抽象,识别它擦除的细节,并确认没有合法客户端需要恢复这些细节。
  • 同时检查隐藏在接口背后的工作:复制、排序或间接访问在实际数据规模下可能成为主导因素。
  • 通过一个部分知道的内容来评估耦合:父类细节、具体类、接口、调用签名或简单值。
  • 不要混淆类型化依赖与无依赖:Protocol 避免声明式继承,但其成员仍然是契约。
  • 当客户端只需要一个操作时,优先使用函数签名;不要创建没有用途的多方法接口。
  • 模式通常只是移动依赖而非消除它;找到仍然必须命名具体类的部分。
  • 在接受具体依赖时,确保它被有意识地放置在易于变更的组装点。
  • 不要深入到间接获得的对象的内部细节;通过显式接口请求所需服务。
  • 将服务于同一主题的元素分组到一个组件中;将可以独立演进的想法分开。
  • 优先使用组合来变更协作者,而不继承其实现细节;如果真正的可替代性存在,则保留继承。
  • 检查子类型是否满足基类型客户端的期望,而不仅仅是方法名称。
  • 提取出于相同原因表达相同意图的重复;偶然的相似性不足以作为理由。
  • 使常见情况简单、罕见情况可能、接口一致;行为的例外会增加记忆成本。
  • 如果移除一个层既不消除必要行为,也不移除有用的隔离,则移除它;在两个足够好的方案中选简单的。
  • 尽可能使计算纯函数化:相同输入产生相同结果,且没有对外部世界的可观察变化。

沿扩展的两个轴重构

  • 区分添加类型与添加操作;设计往往偏向一方而牺牲另一方。
  • 枚举开放家族所有子类的 match 需要维护列表,并可能静默忽略新类型。
  • 如果操作仅按精确类分组,则使用 type(obj) 作为索引的字典可以避免这种枚举。
  • 不要将此替换视为纯语法:子类会拥有自己的键,这与父类模式不同。
  • 对于在首次出现时创建的组,defaultdict(list) 提供一个新列表;字典注解不会创建此行为。
  • 将统一操作保留为读取相关属性的普通函数;如果算法不按类型变化,分发不增加价值。
  • 当每个类型自然地携带其响应且操作较少时,将方法保留在小型、受控的层次结构中。
  • 考虑使用 functools.singledispatch 来添加按类型变化的操作,而无需修改外部层次结构或将新操作分散到每个类中。
  • 使用 singledispatch 时,实现根据第一个参数选择,并可以根据 MRO 继承;它不是精确类表。
  • 默认响应必须对未注册类型真正有效;否则抛出 NotImplementedError 使遗漏可见。
  • 不要从任意模块添加方法:这会迫使读者在类定义之外寻找其行为。
  • 通过更改的位置和保留的数据来度量子重构,而不仅仅是移除的行数。

使用模式词汇而不强加目录

  • 首先描述问题:创建、接口适配、算法选择、通知、树或状态恢复;名称随后而来。
  • 当函数足以胜任时,将 Strategy 和 Command 简化为传递或存储函数;链可以是一组按顺序尝试的函数。
  • 认识共享模块、类表或迭代协议可能已经实现了 Singleton、Factory 或 Iterator 的意图。
  • 区分 Adapter(转换接口)、Façade(简化子系统)和 Proxy(控制对另一个对象的访问)。
  • 区分 State(根据状态改变行为)与状态机(根据输入选择转换)。
  • 区分从外部注入协作者与在注册表中查找协作者;两者都移除了控制,但暴露依赖的方式不同。
  • 将架构、持久化、消息传递和云相关的名称保持在所描述的意图层次:目录中的存在并不确立实现配方。
  • 不要从仅仅提到 Circuit Breaker、Saga、Repository 或 Event Sourcing 就推断出部署、恢复或一致性建议。

流程

  1. 确定目标 Python 版本、已有惯例和检查项;记录需要保留的行为。
  2. 陈述具体需求以及观察到的变更轴;列出今天受影响的组件。
  3. 检查涉及的共享引用、真值、数值运算和字符串转换。
  4. 编写最小解决方案,然后比较所提议的抽象:擦除的细节、隐藏的工作和移动的依赖。
  5. 分别测试添加类型和添加操作;记录每种变体所需的修改。
  6. 分阶段重构,同时保留行为测试以及输入和输出数据的计数。
  7. 移除已不再需要的层,然后在项目支持的版本上运行测试和检查。

陷阱

  • 程序可能在新类型丢失的情况下产生看似合理的结果:检查数据保留,而不仅仅是输出呈现。
  • 回退分支或默认实现可能隐藏被遗忘的扩展;如果没有正确的中性响应,优先显式失败。
  • 在拥有几个真实案例之前进行泛化会创造没有证据的选择;拒绝投机性的框架。
  • 简短的抽象可能隐藏高成本或擦除不可少的细节;仅凭行数不足以判断其质量。
  • 语料库中的最新语法和 API 不一定在旧项目中可用;不要为了采用它们而提高项目的最低 Python 版本。

验证

  • 每个新类或接口保护一个已命名的需求;没有模式仅因其名称而存在。
  • 条件路径在使用前绑定其变量;身份、修改和相等不被混淆。
  • 零、空值、None、负数以及嵌套数据在区分重要时已被测试。
  • 新的子类不会静默消失,并且精确类或继承区分已被确认。

…

[已截断以适配翻译]

相关技能