Python 3.10+ 推荐用 str | int 替代 Union[str, int],因其更简洁、无需导入、IDE 补全更准、嵌套可读性更强,且是 PEP 604 原生支持的底层类型系统特性,而 Union 已被标记为弃用。

Python 3.10 中直接用 Union 注解类属性是可行的,但更推荐用 | 语法 —— 它等价、更简洁,且能避免 Union 在某些场景下的类型擦除问题。
为什么不用 Union[str, int] 而用 str | int
Python 3.10 引入了 PEP 604,支持用 | 作为联合类型的字面量语法。它不是语法糖,而是底层类型系统原生支持:
-
Union[str, int]在运行时会被归一化为str | int(typing.get_origin()返回types.UnionType),但显式写Union会多一层包装,某些静态检查器(如 mypy 1.0+)对|的推导更准确 - 当属性可能为
None时,str | None比Optional[str]更直观,且与from __future__ import annotations配合更干净(无需导入Optional) -
Union在泛型嵌套中易出错,比如Union[list[str], dict[str, int]]写起来冗长,而list[str] | dict[str, int]一目了然
类属性注解中 | 的实际写法和常见错误
直接在类定义中使用 | 即可,但要注意几个边界情况:
- 类体中不能直接执行带
|的表达式(比如x: str | int = "a" if condition else 42是合法的;但若写成x: str | int = some_func() | other_func(),这里|就是位运算符,不是类型联合 —— 类型注解只在冒号后、等号前生效) - 若属性有默认值,类型必须覆盖所有可能取值:
status: str | int | None = None合法;但status: str | int = None会触发 mypy 报错Invalid default for type "str | int" - 继承时子类重写属性类型,需保持协变:父类定义
data: bytes | str,子类不能窄化为data: str(mypy 默认报错),除非显式标注# type: ignore并确认逻辑安全
和 Any、object 的关键区别在哪
用 | 明确枚举类型,不是为了“能过类型检查”,而是为了约束调用方行为:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“Python免费学习笔记(深入)”;
-
value: str | int表示使用者必须处理两种分支,IDE 可据此提供补全(比如调用.upper()前提示“仅当为str时有效”) -
value: Any彻底放弃类型检查,mypy 不报错也不推导;value: object虽比Any严格,但只保留__class__、__str__等极少数方法,无法访问str或int特有属性 - 性能无差异 —— 类型注解在运行时不生效,但工具链(如 pyright)对
|的路径分析比对Any深得多,能提前暴露value.append(...)这类误用
需要特别注意的兼容性陷阱
即使你只用 Python 3.10+,团队或依赖库可能仍需兼顾旧版本:
- 如果项目要支持 Python |,必须回退到
Union并确保导入:from typing import Union;此时Union[str, int, None]是唯一选择 - 某些老版本类型检查器(如 mypy | 解析不稳,例如
list[str | int] | dict[str, float]可能被误判为list[object] | dict[object, object],建议升级 mypy 或拆分为中间类型别名 -
typing.Union在__annotations__中保留原始结构,而|在运行时是types.UnionType实例 —— 如果你手动反射类型(如序列化注解),需用isinstance(t, types.UnionType)判断,而非get_origin(t) is Union
真正麻烦的从来不是写 str | int,而是后续所有基于这个注解的调用逻辑是否真的覆盖了两种分支 —— 类型系统不会替你写 if isinstance(x, str): ... else: ...,它只负责在漏掉时提醒你。

















