python中的类型标注

类型标注

类型标注 = 给变量、参数、返回值写上类型说明

不会在运行时强制检查,主要给 IDE / 类型检查工具用,用来:

  • 写代码时自动补全、提示参数
  • 提前发现「传错类型」这类问题

为什么需要

没有标注时,调用方不知道该传什么,也容易写出「能跑但不符合预期」的代码:

python 复制代码
# 问题:参数类型不明确
def add(a, b):
    return a + b

# 调用者不知道应该传什么类型
add(1, 2)        # 3
add("1", "2")    # "12"  ------ 这也是合法的,但可能不是预期行为
add([1], [2])    # [1, 2]  ------ 同样合法

# 没有类型提示,难以在编码时发现错误

基础写法

变量

python 复制代码
# 声明变量的类型
name: str = "Alice"
age: int = 25
pi: float = 3.14
is_active: bool = True

# 没有初始值
value: int
value = 10

# Python 是动态语言,类型标注不会强制约束
x: int = "hello"  # 不会报错,但类型检查工具会提示

要点:写了 x: int,运行时照样可以塞字符串;要靠类型检查工具(或 IDE)才会报警。

函数

格式:参数: 类型,返回值写在 -> 后面。

python 复制代码
def greet(name: str, age: int) -> str:
    """函数参数和返回值的类型标注"""
    return f"{name} 今年 {age} 岁

# 调用
greet("Alice", 25)        # 正确
greet("Alice", "25")      # 运行不会报错,但类型检查会警告

如果函数永远不会正常返回 (比如直接退出程序),用 NoReturn

python 复制代码
from typing import NoReturn

def exit_program() -> NoReturn:
    """表示函数永远不会正常返回"""
    import sys
    sys.exit(1)

常用复合类型

Optional 和 Union

Optional:可能有值,也可能没有

Optional[X] = 要么是 X,要么是 None

下面三种写法意思完全一样:

python 复制代码
Optional[str]
Union[str, None]
str | None          # Python 3.10+

典型场景:查找用户 ------ 找到返回名字,找不到返回 None

python 复制代码
from typing import Optional, Union


# Optional:值可以是某个类型,也可以是 None
def find_user(user_id: int) -> Optional[str]:
    """返回用户名,找不到时返回 None"""
    if user_id <= 0:
        return None
    return f"User_{user_id}"

调用时先判断是不是 None,再当字符串用:

python 复制代码
name = find_user(1)
if name is None:
    print("没找到")
else:
    print(name)  # 这里才是确定的 str
Union:多种类型之一

Union[A, B]:可以是 A,也可以是 B

python 复制代码
# Union:值可以是多种类型之一
def parse_value(value: str) -> Union[int, float, str]:
    """尝试将字符串转换为数字,失败则返回原字符串"""
    try:
        if "." in value:
            return float(value)
        return int(value)
    except ValueError:
        return value

新写法也可写成:int | float | str(Python 3.10+)

容器类型

ListDict 等后面用 [] 写明里面装什么:

python 复制代码
from typing import List, Dict, Tuple, Set


# 列表:元素类型
scores: List[int] = [85, 90, 78]
names: List[str] = ["Alice", "Bob", "Charlie"]


# 字典:键类型, 值类型
student_scores: Dict[str, int] = {
    "Alice": 85,
    "Bob": 90,
}


# 元组:固定长度,每个位置类型可不同
point: Tuple[int, int] = (10, 20)
person: Tuple[str, int, bool] = ("Alice", 25, True)


# 集合:元素类型
tags: Set[str] = {"python", "typing", "type-hints"}

Any 和类型别名

  • Any:随便什么类型都行(等于几乎不检查,少用)
  • 类型别名:给复杂类型起个好记的名字
python 复制代码
from typing import Any, TypeAlias


# Any:任意类型,相当于没有类型约束
def log_data(data: Any) -> None:
    print(f"数据: {data}")


# 类型别名,让复杂类型更易读
Vector: TypeAlias = List[float]
Matrix: TypeAlias = List[List[float]]


def dot_product(v1: Vector, v2: Vector) -> float:
    """计算两个向量的点积"""
    return sum(a * b for a, b in zip(v1, v2))

类里怎么标

  • 方法参数、返回值照常写
  • Self:返回「自己这个类型的实例」
  • 类还没定义完就要引用自己时,可写成字符串 "Point"
python 复制代码
from typing import Self


class Point:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

    def move(self, dx: float, dy: float) -> Self:
        """返回移动后的新点"""
        return Point(self.x + dx, self.y + dy)

    def distance_to(self, other: "Point") -> float:
        """计算到另一个点的距离"""
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5


# 使用
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2))  # 5.0

泛型

为什么需要泛型?

假设有一个函数,取出列表的第一个元素:

python 复制代码
# 不用泛型:返回类型丢失了
def get_first(items):
    return items[0]

# 你无法知道返回的到底是什么类型

有了泛型:输入什么类型,输出就是什么类型,类型信息不会丢。


传统写法(Python 3.12 之前)

需要两个核心组件:TypeVarGeneric

TypeVar --- 类型变量

T 代表「未来某个具体类型」,实际使用时才会被确定(类似数学里的未知数 x):

python 复制代码
from typing import TypeVar

T = TypeVar('T')  # 定义一个类型变量
泛型函数
python 复制代码
from typing import TypeVar, List

T = TypeVar('T')

def get_first_item(lst: List[T]) -> T:
    return lst[0]

# 调用时,类型自动推断
num = get_first_item([1, 2, 3])       # num 的类型是 int
text = get_first_item(['a', 'b', 'c']) # text 的类型是 str
泛型类
python 复制代码
from typing import TypeVar, Generic

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, content: T):
        self.content = content

    def get(self) -> T:
        return self.content

# 使用
int_box = Box(42)          # Box[int]
str_box = Box("hello")     # Box[str]

现代写法(Python 3.12+)

可以直接在类 / 函数名后面声明类型参数,不必再单独写 TypeVar + Generic

python 复制代码
# 以前
from typing import TypeVar, Generic
T = TypeVar('T')
class Box(Generic[T]):
    def get(self) -> T: ...

# 现在(Python 3.12+)
class Box[T]:
    def get(self) -> T: ...

# 函数也可以这样写
def get_first[T](lst: list[T]) -> T:
    return lst[0]

更接近 Java / C# 的写法,类型参数不再游离在类 / 函数外面。


TypeVar 的三种约束方式

1. 无约束(任意类型)
python 复制代码
T = TypeVar('T')  # 可以是任何类型
2. bound 约束(必须是某类型的子类)
python 复制代码
S = TypeVar('S', bound=str)  # 必须是 str 或其子类

def print_capitalized(x: S) -> S:
    print(x.capitalize())
    return x
3. 值约束(只能是指定的几种类型之一)
python 复制代码
A = TypeVar('A', str, bytes)  # 只能是 str 或 bytes

def concatenate(x: A, y: A) -> A:
    return x + y

concatenate("hello", "world")  # OK,返回 str
concatenate(b"foo", b"bar")    # OK,返回 bytes
# concatenate("foo", b"bar")   # 类型检查报错,不能混用

日常写业务代码,多数时候用 list[int]str | None 就够了。

自己写「输入什么类型、输出就保持什么类型」的函数 / 类时,才需要泛型。


Callable:参数本身是函数

Callable[[参数类型...], 返回类型] 用来标注「回调函数长什么样」。

python 复制代码
from typing import Callable


def execute_callback(
    callback: Callable[[int, int], int],
    a: int,
    b: int
) -> int:
    """执行回调函数"""
    return callback(a, b)


# 使用
result = execute_callback(lambda x, y: x + y, 3, 5)
print(result)  # 8

读法:Callable[[int, int], int] = 接收两个 int,返回一个 int 的函数。


实际能干什么

1. 用 TypedDict 描述「字典长什么样」

普通 dict 太松;TypedDict 可以规定每个 key 的类型:

python 复制代码
from typing import TypedDict


class UserResponse(TypedDict):
    """API 返回的用户数据结构"""
    id: int
    name: str
    email: str
    is_active: bool


def get_user(user_id: int) -> UserResponse:
    return {
        "id": user_id,
        "name": "Alice",
        "email": "alice@example.com",
        "is_active": True,
    }

某个字段可以不提供时,用 NotRequired

python 复制代码
from typing import TypedDict, NotRequired


class Product(TypedDict):
    id: int
    name: str
    description: NotRequired[str]  # 可选字段,可以不提供

# 两种写法都合法
p1: Product = {"id": 1, "name": "手机"}
p2: Product = {"id": 2, "name": "电脑", "description": "高性能笔记本"}

反过来,希望默认所有字段都可选 时,用 total=False

python 复制代码
class Config(TypedDict, total=False):
    debug: bool
    retry_times: int
    timeout: float

# 所有字段都可以不提供
c: Config = {}
相关推荐
计算机魔术师1 小时前
从硅谷测试到全球铺开,ChatGPT广告的10亿美元秘密
前端
专业抄代码选手2 小时前
08|Fiber 上的 `useState`:状态终于属于具体组件
前端·javascript·react.js
默_笙2 小时前
😭 Vibe Coding 翻车实录:AI 编程为什么必须先写"剧本"
前端·javascript
LEE2 小时前
原来 Claude Code 最厉害的工具,一行代码都不写
前端·后端
parade岁月2 小时前
为了给表格加虚拟滚动而选了 vxe-table?也可以考虑下这个
前端·vue.js
打呵欠的猫2 小时前
一个 Hook 让 AI 每次写文件前自动检查编码规范,违规代码再也提交不进来
前端·ai编程·代码规范
恋猫de小郭2 小时前
Android 17 + OkHttp 5.5.0 ,全新 ECH 下你的 HTTPS 域名可以请求时被安全隐藏
android·前端·flutter
后除3 小时前
从零到一: 创建一个 TypeScript 7 项目
前端·webpack·typescript
lerhxx3 小时前
我用 R3F 手搓了一个能走进去的 3D 迷宫简历(上):从选型架构到迷宫生成算法
前端·javascript·three.js