类型注解

一、 前言:为什么 Python 也需要“强类型约束感”?

Python 天生是一门「动态到随心所欲」的语言——不用写变量类型就能跑,写个爬虫脚本20分钟能出结果。但当你带着这种习惯写1000行以上的协作代码、甚至搞后端API时,这种“爽点”很快会变成痛点:

举个上周踩过的小坑🌰:同事优化了登录接口返回的 user_meta 字段,把其中的 levelint 改成了带进度条信息的 dict,结果导出Excel表的脚本直接报了一串 TypeError: unsupported operand type(s) for +: 'dict' and 'int',翻了半小时Git提交记录才定位到漏改的一行。

类似的日常还有:

  1. “变量猜谜大会”:拿到别人写的函数,参数叫 data,返回值叫 result——到底是 listdict?还是随时可能炸的 None
  2. IDE 退化成纯文本编辑器:PyCharm/VS Code 连个靠谱的自动补全都弹不出来,全靠自己背类名、函数名;
  3. 全靠测试覆盖补锅:有些隐性Bug(比如字典传错key的类型)只能在特定输入下才会暴露。

类型注解(Type Hints) 就是解决这些问题的「轻量级解药」——它完全不改变Python的动态特性(解释器照样不强制查),但通过“元数据标签”,提前和开发工具、协作的人「说好规矩」:这里要传什么、那里会返回什么。


二、 基础篇:先给变量和函数「贴个小标签」

最核心的语法特别简单:

  • 变量名后加 : 加类型
  • 函数返回值前加 -> 加类型

1. 单值变量与基础函数

# 单值变量(Python 3.6+ 支持直接标注)
user_name: str = "DaomanLab"
user_level: int = 5
is_member: bool = True

# 带输入输出注解的函数
def generate_greeting(name: str, level: int) -> str:
    """生成会员专属欢迎语"""
    if level >= 5:
        return f"🎉 钻石会员 {name} 下午好!"
    return f"👋 普通会员 {name} 下午好!"

# ⚠️ 这是个“静态检查会报错,但解释器能跑通”的演示
# generate_greeting(123, "Gold") 

三、 进阶篇:容器、多状态值的标注

处理 list/dict 或者「可能是A也可能是B」的情况时,我们需要用到 typing 模块的工具(注意:Python 3.9+ 已经把大部分常用工具内置成小写了!)

1. 常用容器类型

# ✅ Python 3.9+ 最推荐的内置写法(无需导入!)
price_list: list[float] = [199.9, 299.9, 399.9]
course_dict: dict[str, bool | float] = {"Python": True, "price": 99.9}
site_coords: tuple[int, int, str] = (30, 120, "Hangzhou")

# 📝 兼容旧版本(Python <3.9)的写法
from typing import List, Dict, Tuple
old_price_list: List[float] = [1.1, 2.2]
old_course_dict: Dict[str, bool | float] = {"Java": False}

2. 多状态与可空值

  • 联合类型(Union):当某个值/参数「是A或者B」时用;
  • 可选类型(Optional):当某个值/参数「可以是A,也可以是None」时用(本质是 Union[A, None] 的简写)
# 先导入(3.10+ 有更简洁的写法,先看兼容性好的)
from typing import Union, Optional

# 📝 兼容性写法(3.5-3.10通用)
def fetch_course(course_id: Union[int, str]) -> Optional[dict]:
    """支持数字ID或字符串ID查课程,查不到返回None"""
    mock_db = {1: "Python进阶", "course-02": "FastAPI入门"}
    if course_id in mock_db:
        return {"title": mock_db[course_id], "status": "published"}
    return None

# ✅ 3.10+ 极简写法(用 | 替代 Union,直接写 None 替代 Optional)
def fetch_course_v2(course_id: int | str) -> dict | None:
    mock_db = {1: "Python进阶", "course-02": "FastAPI入门"}
    return {"title": mock_db[course_id], "status": "published"} if course_id in mock_db else None

四、 核心篇:类、回调函数与Pydantic模型

1. 标注回调函数(Callable)

Python 经常会把「函数作为参数传进去」(比如排序的key函数、异步框架的回调),这时可以用 Callable 标注:

from typing import Callable

# 标注规则:Callable[[参数1类型, 参数2类型...], 返回值类型]
def apply_math_op(a: int, b: int, op_func: Callable[[int, int], int]) -> int:
    return op_func(a, b)

# 传加法lambda
print(apply_math_op(1, 2, lambda x, y: x + y))  # 输出3
# 传乘法lambda
print(apply_math_op(3, 4, lambda x, y: x * y))  # 输出12

2. Pydantic模型(后端开发的「黄金搭档」)

如果你用 FastAPI 写接口,肯定绕不开 Pydantic——它把类型注解直接变成了“自动数据校验规则”,甚至还能自动生成API文档!

from pydantic import BaseModel

# 1. 定义一个“课程创建请求”的模型
class CourseCreate(BaseModel):
    title: str  # 必填字段
    price: float  # 必填字段
    is_published: bool = False  # 带默认值的选填字段

# 2. 模拟FastAPI的接口逻辑
def create_course_api(req_data: CourseCreate):
    # Pydantic会自动帮你做这些事:
    # ✅ 检查前端传的JSON有没有缺必填字段
    # ✅ 检查字段类型对不对(比如price传了"abc"会直接报错)
    # ✅ 自动把JSON转成Python对象(点语法就能访问属性)
    print(f"✅ 课程创建成功:{req_data.title},定价:{req_data.price}")

# 3. 测试一下(正常输入)
valid_req = CourseCreate(title="Vue3入门", price=49.9)
create_course_api(valid_req)

五、 工程实践:别让注解变成「写代码的负担」

类型注解虽好,但过度使用反而会拖慢开发效率,这里给3个实用建议:

1. 别给“一眼能看出来”的局部变量加注解

比如循环里的 i、函数内部简单推导的 sorted_list,IDE 能自动推断,完全不用写:

# ❌ 过度注解(没必要)
nums: list[int] = [3,1,2]
sorted_nums: list[int] = sorted(nums)
for i: int in range(len(sorted_nums)):
    print(sorted_nums[i])

# ✅ 简洁写法(IDE照样能自动补全)
nums = [3,1,2]
sorted_nums = sorted(nums)
for i in range(len(sorted_nums)):
    print(sorted_nums[i])

2. 用 Any 当“逃生舱”

当你实在无法确定类型时(比如解析JS逆向的超复杂嵌套JSON、对接第三方黑盒接口),可以用 Any 告诉静态检查工具「这里先放我一马」:

from typing import Any

def parse_weird_json(raw: str) -> Any:
    """解析第三方的超复杂JSON,暂时无法确定返回类型"""
    import json
    return json.loads(raw)

3. 配合 mypy 做“静态代码体检”

类型注解写了不用检查等于白写!可以在终端安装并运行 mypy 扫描全项目:

# 安装
pip install mypy

# 扫描单个文件
mypy your_script.py

# 扫描整个项目(推荐加上 --strict 参数,更严格)
mypy your_project/ --strict

结语

类型注解是 Python 从「快速脚本语言」向「工业级协作语言」进化的关键一步。它不会让你写代码的速度变慢(反而后期补全、重构会快很多),也不会强制改变Python的灵活性——掌握了它,你的代码会从「能跑就行」变成「清晰、健壮、别人一看就懂」。