欢迎来到 Python 学习计划的第 54 天!🎉
昨天我们掌握了基础类型提示(list[int], dict[str, int]),今天我们将继续深入 Python 3.10+ 的新特性,学习一个能让代码更简洁、更易读的语法——联合类型(Union Types)。
这是类型提示进阶的关键一步,学会它,你的代码将更具"现代 Python"风格!
一、什么是联合类型?
联合类型(Union Type)表示一个值可以是多种类型中的任意一种。
在 Python 3.10 之前,我们需要从 typing 模块导入 Union 或 Optional。而 Python 3.10 引入了 PEP 604,允许直接使用 | 运算符来表示联合类型。
1. 核心语法
# Python 3.10+ 语法def process(value: int | str) -> str: return str(value)# 可以接受 int 或 strprocess(42) # ✅ OKprocess("hello") # ✅ OK
2. 新旧语法对比
场景 | Python 3.10+ (新) | Python 3.9 及更早 (旧) |
|---|
联合类型 | int | str
| Union[int, str]
|
可选类型 | str | None
| Optional[str]
|
多类型联合 | int | str | float
| Union[int, str, float]
|
导入需求 | 无需导入 | from typing import Union
|
# ❌ 旧写法(繁琐)from typing import Union, Optionaldef old_style(value: Union[int, str]) -> str: return str(value)# ✅ 新写法(简洁)def new_style(value: int | str) -> str: return str(value)
💡 优势:| 符号直观表达“或”的关系,无需导入 typing 模块,代码更短更清晰。
二、基本用法场景
1. 函数参数
当函数需要接受多种类型的参数时,联合类型非常有用。
def format_value(value: int | float | str) -> str: """接受整数、浮点数或字符串""" return f"Value: {value}"format_value(42) # ✅format_value(3.14) # ✅format_value("hello") # ✅
2. 返回值
当函数可能返回不同类型的结果时(例如解析失败返回 None)。
def parse_number(text: str) -> int | float | None: """尝试解析数字,失败返回 None""" try: if "." in text: return float(text) return int(text) except ValueError: return Noneresult = parse_number("42") # 返回 intresult = parse_number("3.14") # 返回 floatresult = parse_number("abc") # 返回 None
3. 变量注解
变量可以在不同时刻存储不同类型,或者可能为空。
# 变量可以是多种类型user_id: int | str = 123user_id = "ABC123" # ✅ 也 OK# 可能为 Nonename: str | None = Nonename = "张三" # ✅ 合法
三、可选类型(Optional)
str | None 等价于 Optional[str],表示值可以是指定类型或 None。这是实际开发中最常见的用法。
# ✅ Python 3.10+ 推荐写法def get_user(user_id: int) -> dict | None: if user_id == 1: return {"name": "张三", "age": 25} return None# 等价于旧写法from typing import Optionaldef get_user_old(user_id: int) -> Optional[dict]: if user_id == 1: return {"name": "张三", "age": 25} return None
四、与集合类型结合
联合类型可以嵌套在列表、字典等容器类型中,描述更复杂的数据结构。
# 列表元素可以是多种类型items: list[int | str] = [1, "two", 3, "four"]# 字典值可以是多种类型config: dict[str, int | str | bool] = { "name": "MyApp", "port": 8080, "debug": True}# 可能返回列表或 Nonedef get_items() -> list[str] | None: return ["a", "b", "c"]
五、在 isinstance 中使用
Python 3.10+ 允许在 isinstance() 检查中直接使用 | 运算符,无需再写元组。
value = 42# ✅ Python 3.10+if isinstance(value, int | str): print("是整数或字符串")# 等价于旧写法if isinstance(value, (int, str)): print("是整数或字符串")
六、实际应用示例
1. 配置解析
配置值的类型往往不固定,联合类型能准确描述这种情况。
def get_config_value(key: str) -> str | int | float | bool | None: """获取配置值,类型可能不同""" config = { "host": "localhost", "port": 8080, "timeout": 30.5, "debug": True } return config.get(key)host = get_config_value("host") # strport = get_config_value("port") # inttimeout = get_config_value("timeout")# floatmissing = get_config_value("xyz") # None
2. API 响应处理
API 返回的数据结构可能随接口不同而变化。
def fetch_data(url: str) -> dict | list | None: """获取 API 数据,可能返回对象、数组或 None""" if "user" in url: return {"name": "张三", "age": 25} elif "users" in url: return [{"name": "张三"}, {"name": "李四"}] return Nonedef process_response(data: dict | list | None) -> str: if data is None: return "无数据" elif isinstance(data, dict): return f"单个对象:{data}" else: return f"列表:{len(data)}项"
3. 数据验证
结合类型守卫,我们可以根据传入类型执行不同的验证逻辑。
def validate_input(value: int | str | None) -> tuple[bool, str]: """验证输入,返回 (是否有效,消息)""" if value is None: return False, "值不能为空" if isinstance(value, int): if value < 0: return False, "数值不能为负" return True, f"有效数值:{value}" if isinstance(value, str): if len(value) == 0: return False, "字符串不能为空" return True, f"有效字符串:{value}" return False, "未知类型"
七、类型守卫(Type Guards)
使用条件判断后,类型检查器会自动缩小类型范围,这在处理联合类型时非常重要。
def process(value: int | str | None) -> str: if value is None: # 这里 value 的类型被推断为 None return "空值" if isinstance(value, int): # 这里 value 的类型被推断为 int return f"数字:{value * 2}" # 这里 value 的类型被推断为 str return f"字符串:{value.upper()}"
八、版本兼容性写法
如果你的代码需要同时支持 Python 3.9 和 3.10+,有两种方案:
1. 使用 __future__(推荐)
这会推迟注解的求值,允许在 3.7+ 中使用 | 语法。
from __future__ import annotations # 必须放在文件第一行def process(value: "int | str") -> "str | None": return str(value)
2. 继续使用 typing 模块
为了最大兼容性(尤其是需要运行时类型检查时),继续使用 Union 和 Optional。
from typing import Union, Optionaldef process(value: Union[int, str]) -> Optional[str]: return str(value)
九、注意事项与最佳实践
1. 避免过多类型
联合类型过多可能表示设计问题,建议拆分函数。
# ❌ 不推荐:类型过多def process(value: int | str | float | list | dict | tuple | None) -> ...: pass# ✅ 推荐:使用更具体的类型或重新设计def process_number(value: int | float) -> ...: passdef process_text(value: str) -> ...: pass
2. 版本要求
| 语法原生需要 Python 3.10+。旧项目可能需要使用 Union。
3. 嵌套联合
嵌套联合类型会被自动展平。
type1: int | (str | float) = 1 # 等价于 int | str | float
十、总结
知识点 | 说明 |
|---|
新语法 | int | str 替代 Union[int, str]
|
可选类型 | str | None 替代 Optional[str]
|
优势 | 无需导入,更简洁直观 |
Isinstance | 3.10+ 支持 isinstance(x, int | str) |
兼容性 | 旧版本需 from __future__ import annotations |
建议 | 避免联合过多类型,保持设计清晰 |
Python 3.10 引入的 | 运算符是对 typing.Union 的语法糖,让类型提示更简洁易读。类型检查器会根据条件判断自动缩小类型范围(类型守卫)。联合类型适用于多类型参数、可选值、动态返回值等场景。
📌 明日预告:类型别名
明天我们将进入 类型提示模块第四天!
- 主题:类型别名(Type Aliases)
- 核心问题:
- 如何简化复杂类型(如
dict[str, list[int \| str]])? TypeAlias 是什么时候引入的?- 类型别名会在运行时创建新类型吗?
💡 提前思考: 如果一个类型签名太长,每次写都很麻烦,该怎么简化?
掌握联合类型新语法,让你的类型提示更现代!继续加油!🚀