0.8 类型系统
概述
你已经熟悉 TypeScript:类型注解、interface、泛型、Optional、tsc 编译期检查。Python 从 3.5+ 开始支持类型注解(type hints,PEP 484),但有一个关键差异:Python 类型注解默认不做任何运行时/编译期检查,需要用外部工具(mypy、pyright)来校验。这一章帮你把 TS 的类型思维映射到 Python。
JS/TS ↔ Python 对照表
核心概念
1. TypeScript vs Python 类型注解(PEP 484)
最大的认知差异:TS 类型是强制的、编译时检查;Python 类型注解是"注释",运行时不检查、不强制。
// TS:类型错误会导致编译失败
let count: number = "hello"; // ❌ 编译错误
# Python:类型注解不阻止运行
count: int = "hello" # ✅ 正常运行,不会报错
print(count) # hello
Python 的类型注解只是元数据,解释器完全忽略它。
要真正检查,需要外部工具:mypy(微软/社区主流)或 pyright(微软,VSCode 的 Pylance 背后)。
2. 基本类型注解
// TS
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let items: number[] = [1, 2, 3];
# Python
name: str = "Alice"
age: int = 30
active: bool = True
items: list[int] = [1, 2, 3]
类型名对照:string→str,number→int/float,boolean→bool。
函数注解:
// TS
function add(a: number, b: number): number {
return a + b;
}
# Python
def add(a: int, b: int) -> int:
return a + b
3. Optional / Union 用 |
// TS
let id: string | null = null;
let value: string | number = 42;
# Python 3.10+
id_: str | None = None
value: str | int = 42
|语法是 Python 3.10+ 推荐的现代写法。旧写法:
Optional[str](=str | None)、Union[str, int],新代码直接用|。
4. 泛型 typing.Generic vs TS 泛型
// TS
function identity<T>(x: T): T {
return x;
}
# Python
from typing import TypeVar
T = TypeVar("T")
def identity(x: T) -> T:
return x
泛型类:
// TS
class Box<T> {
constructor(public value: T) {}
}
# Python
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T):
self.value = value
box: Box[int] = Box(42)
TypeVar定义类型变量,Generic[T]声明泛型类。Python 3.12 有更简洁的
class Box[T]:新语法。
5. TypedDict vs interface
描述"对象的形状"(键值对结构),用 TypedDict:
// TS:interface
interface User {
name: string;
age: number;
}
const u: User = { name: "Alice", age: 30 };
# Python:TypedDict
from typing import TypedDict
class User(TypedDict):
name: str
age: int
u: User = {"name": "Alice", "age": 30}
TypedDict只描述dict的形状,不能定义方法(对应 TS 的interface作为数据形状的用法)。可选键用
total=False或NotRequired。
6. Protocol vs interface
要描述"带方法的接口"(鸭子类型),用 Protocol:
// TS:interface 描述行为
interface HasName {
name: string;
getName(): string;
}
# Python:Protocol 结构化类型
from typing import Protocol
class HasName(Protocol):
name: str
def get_name(self) -> str: ...
obj: HasName = SomeClass() # 只要结构匹配即可
Protocol实现结构化子类型(structural typing),类似 TS 的"结构兼容",不用显式implements。类比:TS 的 interface 是结构化类型,Python 的
Protocol是它最接近的对应。
7. 运行时校验工具 mypy / pyright vs tsc
# 安装与运行类型检查
uv add --dev mypy
uv run mypy src/ # 类似 npx tsc --noEmit
mypy是最主流的类型检查器,pyright更快(微软出品,Pylance 底层)。运行方式类似
tsc --noEmit:只检查,不改变运行时行为。
8. dataclass 配合类型
dataclass + 类型注解是 Python 里定义"结构化数据"的最佳组合(对应 TS 的 interface + 自动构造):
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str | None = None # 可选字段带默认值
u = User("Alice", 30)
u2 = User("Bob", age=25, email="bob@example.com")
代码示例
从 TS 迁移一个用户模型到 Python
// TS
interface User {
id: number;
name: string;
email?: string;
roles: string[];
}
function getUserName(u: User): string {
return u.name;
}
# Python
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str | None = None
roles: list[str] = field(default_factory=list)
def get_user_name(u: User) -> str:
return u.name
泛型函数对照
// TS
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
# Python
from typing import TypeVar, Sequence
T = TypeVar("T")
def first(arr: Sequence[T]) -> T | None:
return arr[0] if arr else None
最佳实践
新代码尽量加类型注解,至少给公共函数签名和数据类标注,配合 mypy/pyright 检查。
用
|和list[int]等现代语法(3.10+),别用老式Optional/List。数据容器用
@dataclass+ 注解,字段默认值用field(default_factory=...)。描述纯数据形状用
TypedDict,描述行为接口用Protocol,描述实体用@dataclass。把
mypy或pyright接入 CI(类似跑tsc),从strict模式开始逐步收敛。运行时校验交给
pydantic(对应 zod),类型注解和运行时校验是两回事,别混淆。
前端开发者常见陷阱
以为类型注解会检查错误:Python 注解不检查、不强制,忘了这点会写出"看起来有类型其实没保障"的代码。
list和List混淆:3.9+ 用内置list[int],老代码用typing.List,混用会困惑(新代码统一用内置)。None可选没标:str | None漏写| None,mypy 会报错(类似 TS 的 strict null check)。TypeVar语法怪:T = TypeVar("T")字符串参数是为了反射,别疑惑为什么传字符串。TypedDict不能有方法:它只是 dict 形状,要行为就用Protocol或普通 class。没有可选链
?.:Python 没有?.,要obj and obj.attr或显式if obj is not None。cast不是真转换:cast(int, x)只骗过类型检查器,运行时不变换任何东西。Any泛滥:图省事全标Any,等于没类型,和 TS 的any一样是坏味道。dataclass可变默认值:roles: list[str] = []会报错/有陷阱,必须field(default_factory=list)。类型注解与运行时值不一致:注解说
int,运行时传入str也不会报错,所以数据入口要用 pydantic 兜底。
总结
Python 类型注解(PEP 484)只是元数据,运行时不检查,检查靠 mypy/pyright(类比
tsc --noEmit)。语法对应:
str/int/bool、list[int]、str | None、-> int与 TS 高度相似。TypedDict≈ 数据形状 interface,Protocol≈ 行为 interface,@dataclass≈ 实体定义。泛型用
TypeVar+Generic[T],3.12+ 有更简洁的class Box[T]语法。类型注解管静态检查,
pydantic管运行时校验,两者配合才完整。
0.8-类型系统
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法