第8章: 类型系统 — Python的类型注解革命
8.1 类型注解基础
Java/Kotlin 对比
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String greet() {
return "Hi, " + this.name;
}
}
class User(val name: String, val age: Int) {
fun greet(): String = "Hi, $name"
}
Python 实现
def greet(name: str, age: int) -> str:
return f"Hi, {name}, age {age}"
result = greet("Alice", 30)
result2 = greet("Bob", "thirty")
result3 = greet(42, None)
name: str = "Alice"
age: int = 30
scores: list[float] = [98.5, 87.3, 95.0]
address: str
class User:
name: str
age: int
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def greet(self) -> str:
return f"Hi, {self.name}"
from typing import get_type_hints
class Config:
host: str
port: int
debug: bool
def __init__(self, host: str, port: int = 8080) -> None:
self.host = host
self.port = port
hints = get_type_hints(Config.__init__)
print(hints)
class_hints = get_type_hints(Config)
print(class_hints)
print(Config.__annotations__)
核心差异
| 维度 | Java/Kotlin | Python |
|---|
| 类型检查时机 | 编译期,强制 | 运行时默认忽略,mypy/pyright 外部检查 |
| 类型错误后果 | 编译失败,无法运行 | 正常运行,类型检查器报警 |
| 注解本质 | 语言语法的一部分 | 特殊属性 __annotations__,本质是字典 |
| 性能影响 | 零(编译期处理) | 微小(注解存储在字典中) |
| 前向引用 | 编译器自动处理 | 需要字符串引号或 from __future__ import annotations |
常见陷阱
x: int = "hello"
class Bad:
name: str
b = Bad()
class Node:
def __init__(self, value: int, next: "Node | None" = None) -> None:
self.value = value
self.next = next
def foo(x=42):
pass
print(foo.__annotations__)
何时使用
- 必须用: 公共 API、团队协作项目、数据处理管道
- 推荐用: 任何超过 200 行的文件
- 可以不用: 一次性脚本、Jupyter notebook 探索性分析
8.2 typing 核心: Optional, Union, Literal, Any, NoReturn
Java/Kotlin 对比
import java.util.Optional;
public class UserService {
public Optional<User> findUser(String name) {
return Optional.ofNullable(db.get(name));
}
public void updateName(@Nullable String newName) {
if (newName != null) {
}
}
}
fun findUser(name: String): User? {
return db[name]
}
val user = findUser("Alice")
println(user?.name)
println(user?.name ?: "N/A")
Python 实现
from typing import Optional, Union, Literal, Any, NoReturn
def find_user(name: str) -> Optional[str]:
"""找不到返回 None"""
users = {"Alice": "alice@example.com", "Bob": "bob@example.com"}
return users.get(name)
email = find_user("Alice")
email2 = find_user("Eve")
def process(value: Union[int, str, float]) -> str:
"""接受 int、str 或 float"""
if isinstance(value, int):
return f"integer: {value}"
elif isinstance(value, str):
return f"string: {value}"
else:
return f"float: {value}"
def process_modern(value: int | str | float) -> str:
if isinstance(value, int):
return f"integer: {value}"
elif isinstance(value, str):
return f"string: {value}"
else:
return f"float: {value}"
def find_user_modern(name: str) -> str | None:
return {"Alice": "alice@example.com"}.get(name)
def set_mode(mode: Literal["debug", "info", "error"]) -> None:
print(f"Mode set to: {mode}")
set_mode("debug")
set_mode("info")
def configure(key: Literal["host", "port"], value: str | int) -> None:
print(f"Setting {key} = {value}")
from typing import Any
def process_any(data: Any) -> Any:
"""Any 是顶级类型,任何值都能赋给它,它也能赋给任何类型"""
return data
x: int = process_any("hello")
import sys
def fail(message: str) -> NoReturn:
"""抛异常或退出进程,永远不会 return"""
print(f"FATAL: {message}")
sys.exit(1)
def assert_positive(value: int) -> None:
if value < 0:
fail(f"Expected positive, got {value}")
print(f"OK: {value}")
from typing import TypedDict
class ServerConfig(TypedDict):
host: str
port: Literal[80, 443, 8080, 8443]
ssl: bool
db: Union[str, None]
config: ServerConfig = {
"host": "localhost",
"port": 8080,
"ssl": False,
"db": None,
}
核心差异
| 概念 | Java/Kotlin | Python |
|---|
| 可空 | Kotlin T? / Java @Nullable | Optional[T] 或 T | None |
| 多类型 | Java 无原生支持 | Union[A, B] 或 A | B |
| 字面值约束 | 无原生支持 | Literal["a", "b"] |
| 顶级类型 | Object / Any | Any(会污染类型) |
| 不返回 | 无对应概念 | NoReturn |
常见陷阱
def get_length(s: Optional[str]) -> int:
return len(s)
def bad(x: Any) -> int:
return x
Union[int, str] == Union[str, int]
def handle(value: int | str) -> int:
if isinstance(value, str):
return len(value)
return value
何时使用
Optional / T | None: 函数可能不返回有效值时
Union: API 需要兼容多种输入时
Literal: 限定枚举值、状态机、配置项
Any: 遗留代码迁移、动态数据(JSON)的临时方案——尽快消除
NoReturn: 异常抛出函数、进程退出函数
8.3 泛型: TypeVar, Generic, Protocol
Java/Kotlin 对比
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
}
Box<String> box = new Box<>("hello");
public class NumberBox<T extends Number> {
private T value;
public double doubleValue() { return value.doubleValue(); }
}
class Box<out T>(val value: T)
class MutableBox<T>(var value: T)
fun <T : Comparable<T>> maxOf(a: T, b: T): T =
if (a >= b) a else b
fun process(box: Box<*>) {
val value: Any? = box.value
}
Python 实现
from typing import TypeVar, Generic, Protocol, TypeGuard
from collections.abc import Iterable
T = TypeVar("T")
N = TypeVar("N", int, float)
C = TypeVar("C", bound="Comparable")
def first(lst: list[T]) -> T:
"""返回列表第一个元素"""
return lst[0]
x: int = first([1, 2, 3])
s: str = first(["hello", "world"])
def add(a: N, b: N) -> N:
"""两个数字相加"""
return a + b
result: int = add(1, 2)
result2: float = add(1.5, 2.5)
class Box(Generic[T]):
"""泛型容器"""
def __init__(self, value: T) -> None:
self._value = value
def get(self) -> T:
return self._value
def set(self, value: T) -> None:
self._value = value
int_box: Box[int] = Box(42)
str_box: Box[str] = Box("hello")
int_box.set(100)
val: int = int_box.get()
K = TypeVar("K")
V = TypeVar("V")
class Pair(Generic[K, V]):
def __init__(self, key: K, value: V) -> None:
self.key = key
self.value = value
pair: Pair[str, int] = Pair("age", 30)
class Drawable(Protocol):
"""任何有 draw() 方法的类都自动满足此协议"""
def draw(self) -> str: ...
class Circle:
def draw(self) -> str:
return "○"
class Rectangle:
def draw(self) -> str:
return "□"
class NotDrawable:
def render(self) -> str:
return "???"
def render_all(items: list[Drawable]) -> None:
for item in items:
print(item.draw())
render_all([Circle(), Rectangle()])
class Named(Protocol):
name: str
class Person:
def __init__(self, name: str) -> None:
self.name = name
class Company:
def __init__(self, name: str) -> None:
self.name = name
def get_name(entity: Named) -> str:
return entity.name
print(get_name(Person("Alice")))
print(get_name(Company("Google")))
from typing import TypeGuard
def is_int_list(val: list) -> TypeGuard[list[int]]:
"""告诉 mypy: 如果返回 True,val 就是 list[int]"""
return all(isinstance(x, int) for x in val)
def process_numbers(items: list[int | str]) -> None:
if is_int_list(items):
total = sum(items)
print(f"Sum: {total}")
核心差异
| 维度 | Java/Kotlin | Python |
|---|
| 泛型实现 | 编译期检查,运行时擦除 | 纯运行时属性,mypy 检查 |
| 类型约束 | extends / : | bound= 参数 |
| 协变/逆变 | out T / in T(声明处) | covariant=True / contravariant=True |
| 结构化类型 | 无(必须显式 implements) | Protocol(鸭子类型的静态版) |
| 泛型实例 | 运行时不可见 | 运行时可通过 __orig_class__ 查看 |
常见陷阱
T = TypeVar("T")
class BadBox:
def __init__(self, value: T) -> None:
self._value = value
class GoodBox(Generic[T]):
def __init__(self, value: T) -> None:
self._value = value
from typing import runtime_checkable
@runtime_checkable
class CheckableDrawable(Protocol):
def draw(self) -> str: ...
print(isinstance(Circle(), CheckableDrawable))
何时使用
TypeVar: 函数/类需要保持输入输出类型一致时
Generic: 需要类型参数化的容器类
Protocol: 定义接口但不强制继承关系时——Python 的杀手特性
TypeGuard: 自定义类型收窄逻辑时
8.4 PEP 695 (3.12+): 新泛型语法
Java/Kotlin 对比
public <T> T first(List<T> list) {
return list.get(0);
}
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
}
fun <T> first(lst: List<T>): T = lst.first()
class Box<T>(val value: T) {
fun get(): T = value
}
Python 实现
def first[T](lst: list[T]) -> T:
"""返回列表第一个元素"""
return lst[0]
x: int = first([1, 2, 3])
s: str = first(["hello"])
class Box[T]:
def __init__(self, value: T) -> None:
self._value = value
def get(self) -> T:
return self._value
def set(self, value: T) -> None:
self._value = value
int_box: Box[int] = Box(42)
str_box: Box[str] = Box("hello")
class Pair[K, V]:
def __init__(self, key: K, value: V) -> None:
self.key = key
self.value = value
pair: Pair[str, int] = Pair("age", 30)
def add[T: (int, float)](a: T, b: T) -> T:
"""T 必须是 int 或 float"""
return a + b
class Container[T: object]:
"""T 必须是 object 的子类型(几乎所有类型都满足)"""
def __init__(self, value: T) -> None:
self._value = value
class Result[T, E: Exception = Exception]:
"""类似 Rust 的 Result<T, E>,E 默认为 Exception"""
def __init__(self, value: T | None = None, error: E | None = None) -> None:
self._value = value
self._error = error
def is_ok(self) -> bool:
return self._error is None
def unwrap(self) -> T:
if self._error is not None:
raise self._error
return self._value
ok: Result[int] = Result(value=42)
err: Result[int, ValueError] = Result(error=ValueError("bad"))
from typing import TypeVar, Generic
T_old = TypeVar("T_old")
def first_old(lst: list[T_old]) -> T_old:
return lst[0]
class Box_old(Generic[T_old]):
def __init__(self, value: T_old) -> None:
self._value = value
def first_new[T](lst: list[T]) -> T:
return lst[0]
class Box_new[T]:
def __init__(self, value: T) -> None:
self._value = value
核心差异
| 维度 | Java/Kotlin | Python 3.12+ |
|---|
| 泛型函数声明 | <T> def first(...) / fun <T> first(...) | def first[T](...) |
| 泛型类声明 | class Box<T> / class Box<T> | class Box[T] |
| 约束语法 | T extends Number / T : Number | T: (int, float) 或 T: bound |
| 默认类型参数 | 不支持 | 3.13+ 支持 |
| 作用域 | 类/方法内 | 类/函数内(新语法),全局(旧语法) |
常见陷阱
T = TypeVar("T")
何时使用
- Python 3.12+: 优先使用新语法,更简洁、作用域更清晰
- Python 3.10/3.11: 只能用旧语法(TypeVar + Generic)
- 跨版本兼容: 用旧语法,或
typing_extensions 库
8.5 type 语句 (3.12+): 类型别名
Java/Kotlin 对比
public class UserID extends String {
}
typealias UserID = String
typealias Point = Pair<Double, Double>
typealias Handler = (Event) -> Unit
fun lookup(id: UserID): User? { ... }
Python 实现
type UserID = str
type Point = tuple[float, float]
type Score = int
def lookup(user_id: UserID) -> Point:
"""UserID 和 Point 都是类型别名,运行时就是 str 和 tuple"""
return (0.0, 0.0)
uid: UserID = "alice-001"
pos: Point = (1.0, 2.0)
score: Score = 95
type Matrix[T] = list[list[T]]
type Mapping[K, V] = dict[K, V]
type Result[T] = tuple[bool, T | None]
int_matrix: Matrix[int] = [[1, 2], [3, 4]]
str_matrix: Matrix[str] = [["a", "b"], ["c", "d"]]
name_map: Mapping[str, int] = {"Alice": 30, "Bob": 25}
result: Result[str] = (True, "success")
type JSON = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
type Headers = dict[str, str]
type Handler = Callable[[str], bool]
def process(data: JSON, headers: Headers) -> None:
"""类型别名让签名清晰可读"""
pass
from typing import TypeAlias
PointOld: TypeAlias = tuple[float, float]
UserIDOld: TypeAlias = str
PointOld2 = tuple[float, float]
type JSONObject = dict[str, "JSONValue"]
type JSONValue = str | int | float | bool | None | JSONObject | list["JSONValue"]
type X = int | str
Y = int | str
核心差异
| 维度 | Java/Kotlin | Python 3.12+ |
|---|
| 语法 | typealias X = Y | type X = Y |
| 泛型别名 | typealias Matrix<T> = List<List<T>> | type Matrix[T] = list[list[T]] |
| 递归别名 | 支持 | 支持(需前向引用) |
| 运行时行为 | 编译后完全擦除 | 运行时是原类型的别名 |
| 旧版兼容 | N/A | TypeAlias 注解或直接赋值 |
常见陷阱
type UserID = str
uid: UserID = "alice"
print(type(uid))
print(isinstance(uid, str))
type UserID = str
uid: UserID = "alice"
uid2: UserID = 123
何时使用
- 复杂联合类型需要命名以提高可读性
- 领域概念需要类型级别的语义(如
UserID vs str)
- 泛型容器类型需要参数化别名(如
Matrix[int])
8.6 ParamSpec 与 TypeVarTuple: 高级泛型
Java/Kotlin 对比
public interface Function<T, R> {
R apply(T t);
}
public double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
fun process(list: List<*>) {
}
inline fun <reified T> typeName(): String = T::class.simpleName ?: "Unknown"
Python 实现
from typing import (
ParamSpec, TypeVarTuple, Concatenate,
Callable, Any
)
from collections.abc import Callable as AbcCallable
P = ParamSpec("P")
R = TypeVar("R")
def log_calls(func: Callable[P, R]) -> Callable[P, R]:
"""装饰器: 记录函数调用,ParamSpec 保留原始参数签名"""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_calls
def add(a: int, b: int) -> int:
return a + b
@log_calls
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
result: int = add(1, 2)
def with_retry(
func: Callable[P, R],
max_retries: int = 3
) -> Callable[Concatenate[int, P], R]:
"""
装饰器工厂: 在原函数参数前添加一个 timeout 参数
Concatenate[int, P] 表示 (int, *原始参数)
"""
def wrapper(timeout: int, *args: P.args, **kwargs: P.kwargs) -> R:
last_error: Exception | None = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
print(f"Attempt {attempt + 1} failed: {e}")
raise last_error
return wrapper
@with_retry(max_retries=5)
def fetch_data(url: str) -> str:
"""被装饰后签名变成 (timeout: int, url: str) -> str"""
return f"Data from {url}"
data: str = fetch_data(timeout=10, url="https://example.com")
Ts = TypeVarTuple("Ts")
def flatten(*arrays: *Ts) -> list:
"""将多个列表展平为一个列表"""
result: list = []
for arr in arrays:
result.extend(arr)
return result
result = flatten([1, 2], [3, 4], [5, 6])
import numpy as np
from typing import TypeVarTuple
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
def create_logger(prefix: str) -> Callable[[Callable[P, R]], Callable[P, R]]:
"""工厂函数返回装饰器,ParamSpec 保留签名"""
def decorator(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"[{prefix}] {func.__name__} called")
return func(*args, **kwargs)
return wrapper
return decorator
@create_logger("API")
def get_user(user_id: int, detailed: bool = False) -> dict[str, str]:
return {"id": str(user_id), "detail": "full" if detailed else "basic"}
info: dict[str, str] = get_user(42, detailed=True)
ParamSpec 实战: 装饰器保留函数签名
from typing import ParamSpec, TypeVar, Callable
from functools import wraps
P = ParamSpec('P')
R = TypeVar('R')
def log_calls(func: Callable[P, R]) -> Callable[P, R]:
"""装饰器: 记录函数调用,完整保留原函数的签名"""
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"调用 {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"返回: {result}")
return result
return wrapper
@log_calls
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}! " * times
result: str = greet("Alice", times=3)
from typing import Concatenate
def with_db(func: Callable[Concatenate[dict, P], R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
db = {"connection": "active"}
return func(db, *args, **kwargs)
return wrapper
@with_db
def get_user(db: dict, user_id: int) -> str:
return f"User {user_id} from {db['connection']}"
result = get_user(42)
print(result)
TypeVarTuple 实战: 可变长度泛型
from typing import TypeVarTuple
Ts = TypeVarTuple('Ts')
class Array:
def __init__(self, *items):
self.items = items
def __repr__(self):
return f"Array({self.items})"
def flatten(arrays: list[*Ts]) -> list[*Ts]:
"""展平嵌套列表 — TypeVarTuple 保留元素类型"""
result = []
for arr in arrays:
result.extend(arr)
return result
from itertools import islice
def batched(iterable, n):
"""将可迭代对象按 n 个一组分割"""
it = iter(iterable)
while batch := tuple(islice(it, n)):
yield batch
data = list(range(10))
for batch in batched(data, 3):
print(batch)
核心差异
| 维度 | Java/Kotlin | Python |
|---|
| 捕获参数签名 | 不支持 | ParamSpec |
| 添加参数到签名 | 不支持 | Concatenate |
| 可变长度泛型 | 不支持 | TypeVarTuple |
| 通配符 | ? extends T / * | ParamSpec 更强大 |
| reified | Kotlin inline reified | Python 天然 reified(运行时有类型) |
常见陷阱
P = ParamSpec("P")
from typing import Concatenate
Ts = TypeVarTuple("Ts")
def foo(*args: *Ts): ...
何时使用
ParamSpec: 编写保留原函数签名的装饰器时——非常实用
Concatenate: 装饰器需要添加额外参数时
TypeVarTuple: 处理可变长度类型参数(如矩阵、张量)时
8.7 TypedDict, NamedTuple: 结构化类型
Java/Kotlin 对比
public record Point(double x, double y) {}
Map<String, Object> config = Map.of(
"host", "localhost",
"port", 8080
);
data class Point(val x: Double, val y: Double)
val config = mapOf(
"host" to "localhost",
"port" to 8080
)
Python 实现
from typing import TypedDict, NamedTuple, NotRequired, Required
class User(TypedDict):
name: str
age: int
email: str
user: User = {
"name": "Alice",
"age": 30,
"email": "alice@example.com",
}
print(user["name"])
for key, value in user.items():
print(f"{key}: {value}")
class ServerConfig(TypedDict):
host: str
port: int
debug: NotRequired[bool]
ssl: Required[bool]
config: ServerConfig = {
"host": "localhost",
"port": 8080,
"ssl": True,
}
class PartialUser(TypedDict, total=False):
name: str
age: int
email: str
update: PartialUser = {"age": 31}
class Address(TypedDict):
city: str
zip_code: str
class Employee(TypedDict):
name: str
address: Address
emp: Employee = {
"name": "Bob",
"address": {"city": "Beijing", "zip_code": "100000"},
}
class Point(NamedTuple):
x: float
y: float
p = Point(1.0, 2.0)
print(p.x, p.y)
print(p[0], p[1])
print(p)
print(Point(1.0, 2.0) == Point(1.0, 2.0))
x, y = p
print(x, y)
class UserInfo(NamedTuple):
name: str
age: int
email: str = "N/A"
def is_adult(self) -> bool:
return self.age >= 18
info = UserInfo("Alice", 30)
print(info.is_adult())
print(info.email)
class APIError(TypedDict):
code: int
message: str
details: NotRequired[str]
class APIResponse(TypedDict):
success: bool
data: NotRequired[dict[str, object]]
error: NotRequired[APIError]
ok_response: APIResponse = {
"success": True,
"data": {"id": 1, "name": "Alice"},
}
err_response: APIResponse = {
"success": False,
"error": {"code": 404, "message": "Not found"},
}
from dataclasses import dataclass
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
cfg = Config(host="0.0.0.0", port=3000)
cfg.debug = True
核心差异
| 维度 | Java Record | Kotlin data class | Python NamedTuple | Python TypedDict |
|---|
| 可变性 | 不可变 | 可变(var)/不可变(val) | 不可变 | 可变 |
| 位置访问 | componentN() | componentN() | 索引 | 不支持 |
| 命名访问 | getX() | .x | .x | ["x"] |
| 可哈希 | 是 | 是(全 val 时) | 是 | 否 |
| JSON 兼容 | 需要库 | 需要库 | 需要转换 | 原生兼容 |
| 默认值 | 支持 | 支持 | 支持 | 不支持(用 NotRequired) |
常见陷阱
user: User = {"name": 42, "age": "thirty", "email": []}
user = {"name": "Alice", "age": 30, "email": "a@b.com"}
class Bad(NamedTuple, dict):
x: int
class A(TypedDict):
x: NotRequired[int]
class B(TypedDict, total=False):
x: int
class C(TypedDict):
x: Required[int]
何时使用
TypedDict: JSON API 响应、配置字典、需要类型约束的字典
NamedTuple: 不可变数据记录、需要哈希的键、函数返回多值
dataclass: 需要可变性+默认值+方法时(第4章已详述)
8.8 typing.override (3.12+) 与其他装饰器
Java/Kotlin 对比
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Woof!");
}
}
open class Animal {
open fun speak() { println("...") }
}
class Dog : Animal() {
override fun speak() {
println("Woof!")
}
}
Python 实现
import typing
import warnings
class Animal:
def speak(self) -> str:
return "..."
class Dog(Animal):
@typing.override
def speak(self) -> str:
return "Woof!"
class Cat(Animal):
@typing.override
def speak(self) -> str:
return "Meow!"
class Bad(Animal):
@typing.override
def bark(self) -> str:
return "Woof!"
class BaseService:
@typing.final
def authenticate(self, token: str) -> bool:
"""子类不能覆盖此方法"""
return token == "secret"
class UserService(BaseService):
def get_user(self, user_id: int) -> dict[str, str]:
return {"id": str(user_id)}
@typing.final
class Singleton:
"""此类不能被继承"""
pass
class OldAPI:
@typing.deprecated("Use new_method() instead")
def old_method(self) -> str:
"""弃用的方法——mypy 会报警"""
warnings.warn(
"old_method is deprecated, use new_method instead",
DeprecationWarning,
stacklevel=2,
)
return "old result"
def new_method(self) -> str:
return "new result"
api = OldAPI()
api.old_method()
class Controller:
"""Web 控制器基类"""
@typing.final
def handle_request(self, path: str) -> str:
"""模板方法——子类不能覆盖"""
self.before_request()
result = self.dispatch(path)
self.after_request()
return result
def before_request(self) -> None:
"""钩子方法——子类可以覆盖"""
pass
@typing.override
def dispatch(self, path: str) -> str:
"""子类必须覆盖"""
raise NotImplementedError
def after_request(self) -> None:
"""钩子方法——子类可以覆盖"""
pass
class UserController(Controller):
@typing.override
def dispatch(self, path: str) -> str:
if path == "/":
return "User list"
return "User detail"
@typing.override
def before_request(self) -> None:
print("Authenticating...")
from typing_extensions import override, final
class AnimalCompat:
def speak(self) -> str:
return "..."
class DogCompat(AnimalCompat):
@override
def speak(self) -> str:
return "Woof!"
核心差异
| 维度 | Java @Override | Kotlin override | Python @typing.override |
|---|
| 性质 | 注解(可选) | 关键字(必须) | 装饰器(可选) |
| 检查时机 | 编译期 | 编译期 | mypy/pyright |
| 运行时效果 | 无 | 无 | 无(纯类型提示) |
| 未覆盖父类方法 | 编译错误 | 编译错误 | mypy 报错 |
常见陷阱
class Parent:
pass
class Child(Parent):
@typing.override
def foo(self) -> None:
pass
class Parent:
@typing.final
def foo(self) -> None:
pass
class Child(Parent):
def foo(self) -> None:
pass
from typing_extensions import override
何时使用
@override: 所有覆盖父类方法的地方——防止签名错误,提高可读性
@final: 框架/库中不允许子类修改的关键方法
@deprecated: API 迁移期标记旧接口
8.9 mypy/pyright 严格模式配置
Java/Kotlin 对比
Python 实现
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_generics = true
strict_optional = true
strict_equality = true
check_untyped_defs = true
[[tool.mypy.overrides]]
module = [
"requests.*",
"numpy.*",
"pandas.*",
]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
warn_return_any = false
[tool.pyright]
pythonVersion = "3.10"
typeCheckingMode = "standard"
exclude = [
"venv",
".venv",
"build",
"dist",
"node_modules",
]
venvPath = "."
venv = ".venv"
def public_api(user_id: int) -> dict[str, str]:
"""公共函数加注解"""
return {"id": str(user_id)}
def _internal_helper(data):
return process(data)
def legacy_function(x, y):
return x + y
from typing import overload
@overload
def process(value: int) -> int: ...
@overload
def process(value: str) -> str: ...
def process(value: int | str) -> int | str:
"""mypy 根据参数类型选择正确的重载签名"""
if isinstance(value, int):
return value * 2
return value.upper()
核心差异
| 维度 | javac/kotlinc | mypy/pyright |
|---|
| 检查时机 | 编译期(必须通过) | 开发期(可选工具) |
| 配置方式 | 编译器参数 | pyproject.toml / mypy.ini |
| 渐进式采用 | 不支持 | 支持(逐文件/逐模块) |
| 第三方库 | 有源码就有类型 | 需要存根 (.pyi) 或 types-xxx 包 |
| 运行时影响 | 零 | 零(纯静态分析) |
常见陷阱
import requests
values = [1, "hello", 3.14]
for v in values:
pass
何时使用
- 新项目: 从第一天就加类型注解,用
--strict
- 现有项目: 渐进式类型化,先公共 API 后内部代码
- 库/框架: 必须有完整类型注解 + .pyi 存根
- 脚本/工具: 至少给函数签名加注解
- CI/CD: 集成 mypy 到 pre-commit 或 CI pipeline
总结: Python 类型系统的心智模型
Java/Kotlin: 编译器强制类型 → 类型错误 = 编译失败
Python: 类型注解是提示 → mypy/pyright 检查 → 运行时不强制
关键原则:
1. 类型注解写给工具看,不是写给解释器看
2. 渐进式采用——从公共 API 开始
3. Protocol 是 Python 的杀手特性——鸭子类型 + 静态检查
4. 3.12+ 新语法(type 语句、泛型语法)大幅降低样板代码
5. 选择 mypy 或 pyright,不要两个混用