Files
pyflowx/.trae/rules/python-standards.md
zhou cbc7cc0a75 docs: 拆分测试规范到独立技能文档并更新主规范
将原python-standards.md中的测试章节迁移到新建的pyflowx-testing/SKILL.md,更新主规范指向新文档,同时整理优化了整体文档结构与内容。
2026-06-28 10:19:26 +08:00

9.2 KiB
Raw Permalink Blame History

Python 开发规范

本规范结合 Python 最佳实践,作为编写与审查 Python 代码的统一标准。 详细操作指南见 .agents/skills/ 下相应技能。

工具链(以 pyproject.toml 为准)

工具 用途 配置要点
ruff lint + format line-length=120target-version="py38"
pyrefly 类型检查 preset="strict"python-version="3.8"
pytest 测试 asyncio_default_fixture_loop_scope="function"marker slow
coverage 覆盖率 branch=truefail_under=95concurrency=["thread"]
pre-commit 提交前检查 ruff --fix + trailing-whitespace + end-of-file-fixer

验证(每次修改后必做):

uvx --from pyflowx pymake tc
uvx --from pyflowx pymake cov

兼容性

  • 最低 Python 3.8:用 from __future__ import annotations 延迟注解求值; 按版本用 typing.List(3.8) → 内置泛型(3.9) → X | Y(3.10) → typing.override(3.12)。
  • 版本守卫if sys.version_info >= (3, X): 引入高版本 API;低版本回退分支加 # pragma: no cover
  • 零运行时依赖:仅依赖标准库(3.8 需 graphlib_backporttyping-extensions)。 新增依赖须审慎,优先用标准库。

类型注解

  • 公共 API 必须有完整类型注解,包括返回类型;私有函数也应有注解。
  • 泛型用 TypeVarPEP 696 default= 仅 3.13+ 标准库支持,3.83.12 用 typing_extensions.TypeVar
  • Mapping/Sequence 用于只读参数,dict/list 用于可变返回。
  • Any 仅用于真正动态场景(如 Context 跨任务异构映射);任务内部类型必须完全静态。
  • 禁用裸 # type: ignore;确需时加具体规则码(如 # type: ignore[union-attr])。
  • TYPE_CHECKING 守卫:仅类型检查需要的导入放 if TYPE_CHECKING: 块内,避免循环依赖。
  • 类型收窄:用 assert isinstance(x, Y) 辅助 pyrefly 推断;cast() 仅用于类型系统无法表达的场景。

数据结构

  • 不可变优先:配置/描述类用 @dataclass(frozen=True);可变类属性标注 RUF012 豁免。
  • 缓存:实例级用 functools.cached_property,按参数键控用 functools.lru_cache 不可哈希参数需 try/except 回退。修改被缓存数据源后必须手动清空缓存。
  • 抽象基类:接口用 abc.ABC + @abstractmethod(如 StateBackend)。
  • 枚举:状态/标志值用 enum.Enum(如 TaskStatus),禁止裸字符串/魔术数字;枚举值用 UPPER_SNAKE
  • __repr__:可变类实现 __repr__(含关键字段);frozen=True dataclass 自动生成。

模块与导入

  • 单一职责:每模块只做一件事(task.py 数据结构、executors.py 执行、command.py 命令、compose.py 组合)。禁止跨职责边界。
  • 导入顺序ruff isort):__future__ → 标准库 → 第三方 → 本地,各组间空行。
  • 惰性导入:仅为打破循环依赖时使用,函数体内导入并注释说明;顶层导入是默认。
  • __all__:定义 __all__ 显式声明导出符号,位置仅次于 __future__ 之后。
  • 禁用 star importsfrom x import * 污染命名空间、破坏类型检查(__init__.py 聚合经 __all__ 控制为例外)。
  • 避免 utils.py/helpers.py:按职责归入对应模块。

函数设计

  • 模块级函数优于 Mixin:共享逻辑用模块级函数,类只持有状态与薄方法。
  • 静态方法慎用:纯函数直接放模块级。
  • 参数 ≤ 5 个为宜;超出用 dataclass 封装参数对象。
  • 单一职责:一个函数做一件事;过长函数考虑拆分。
  • 异常范围要窄:只捕获预期异常(如 (TypeError, ValueError, KeyError, AttributeError)), 禁止 except Exception 掩盖 bug;捕获后至少 logger.warning 记录。
  • 可变默认参数def f(x=[]) 是经典坑;用 None 哨兵或 field(default_factory=list)

异常处理

  • 自定义异常家族:继承公共基类(如 PyFlowXError),按错误场景分类。
  • 异常包装raise NewError(...) from exc 保留因果链。
  • 不要吞异常:捕获后必须处理(记录/包装/重抛),禁止空 except: pass
  • 钩子/回调异常:第三方回调异常仅记录,不影响主流程。

并发与线程安全

  • 进程全局状态os.environ/os.chdir)在并发场景下必须用全局锁(threading.RLock)序列化。
  • 条件评估不可有可变状态:组合条件(NOT/AND/OR)不得修改共享 _reason,避免竞态。
  • 批量 I/O:循环内多次写盘改为批量一次(contextmanager 包裹延迟落盘)。
  • 信号量限流concurrency_key + Semaphore 按组限流。

测试

详细操作指南见 .agents/skills/pyflowx-testing 技能。硬约束:

  • 覆盖率 ≥ 95%branch coverage),不得下降。
  • 公共 API 优先测试:用公共接口(has/get),不访问私有方法; 故障注入等场景可临时访问私有属性,docstring 注明原因。
  • 命名test_<被测对象>_<场景>
  • 断言:原生 assert x == 1,禁用 self.assertEqualpytest.raises 必填 match=
  • Mock 优先级monkeypatch > 内联 stub > unittest.mock > pytest-mock。 禁用 @patch 装饰器、mock.patch.object 上下文、pytest-mockmocker fixture。
  • fixturetmp_path/monkeypatch/capsys 优先;autouse 仅全局必需时用。
  • slow 标记:耗时测试加 @pytest.mark.slowCI 可 -m "not slow" 跳过。
  • 测试代码也跑 rufftests/** 忽略 ARG001/ARG002

代码风格

  • 行宽 120ruff formatter 处理)。
  • docstring:公共 API 必须有;中文叙述 + 中文注释是本项目既有风格。
  • 打印和日志:使用中文打印和日志,避免使用英文。
  • 命名snake_case 函数/变量,PascalCase 类,UPPER_SNAKE 常量,_leading_underscore 私有。
  • 字符串引号ruff 默认双引号。
  • 末尾单 \n无尾随空格pre-commit 强制)。
  • 不用 emoji:除非用户明确要求。

Pythonic 风格

  • is 比较 None/True/False:单例用 is,值用 ==PEP 8 E711/E712)。
  • EAFP 优于 LBYL:先尝试再处理异常,而非先检查再执行(避免竞态窗口)。
  • truthinessif items: 优于 if len(items) > 0:
  • 字符串格式化:首选 f-string% 仅用于 logging 延迟格式化。
  • 推导式优于 map+filter> 2 层拆为显式循环。
  • enumerate 替代 range(len())zip 并行迭代(3.10+ 用 strict=True)。
  • 解包 a, b = pair 优于索引访问;忽略值用 _
  • 海象运算符 :=(3.8+):赋值+判断合一,但不滥用。

日志

  • logging.getLogger(__name__):每模块独立 logger,禁用 print 调试残留。
  • 结构化上下文extra={...} 传字段;logger.warning("task %r failed: %s", name, exc) 优于 f-string(延迟格式化)。
  • 日志级别DEBUG 诊断 / INFO 关键流程 / WARNING 可恢复异常 / ERROR 需人工介入。
  • 禁止日志密码/密钥:脱敏后再记录。

路径与资源

  • 优先 pathlib.PathPath("a") / "b" 而非 os.path.joinruff PTH 强制); 禁止字符串拼接路径。类型注解用 Path,边界 str 立即包装。
  • with 语句:文件、锁、连接、临时目录一律用 withcontextlib.contextmanager 多资源用 contextlib.ExitStack
  • 显式关闭:长生命周期对象(连接池、线程池)实现 close(),但优先 with
  • 批量操作:循环内多次 acquire/release 改为批量一次。

安全

  • 禁用 eval/exec:处理不可信输入时绝不使用;用 ast.literal_eval 或专用解析器。
  • subprocess:禁用 shell=True 除非命令完全可信;优先 list[str] 形式。
  • 凭证不入仓:密钥/token/密码放 .env 或环境变量,.gitignore 必须包含 .env
  • 日志脱敏:记录请求/响应时移除 Authorizationpassword 等字段。
  • 依赖审计uv lock 后审阅新增依赖,避免引入已知 CVE 的包。

性能要点

  • 避免重复计算:循环内查询应缓存或预构建映射(如 {name: spec})。
  • 避免双重查找has(k) + get(k) 改为单次 get(k) + KeyError 回退。
  • 统一校验:入口校验一次,下游路径不重复(如 run() 统一 validate()layers() 不再重复)。
  • 事件 emit:任务生命周期必须 emit RUNNINGSUCCESS/FAILED/SKIPPED 不要留死分支(# pragma: no cover 是清理信号,应激活或删除)。

Git 与提交

  • 不自动提交/push:除非用户明确要求。
  • 不修改 git config
  • 不运行破坏性命令push --force/reset --hard/clean -f)除非用户明确要求。
  • staging:按文件名添加,不用 git add -A/git add .,避免误加敏感文件。
  • commit message:简洁,聚焦"为什么"而非"是什么";遵循仓库既有风格。