TL;DR Sửa Nhanh
Trên Python 3.8 hoặc cũ hơn, các kiểu dữ liệu tích hợp như list[int] và dict[str, int] không thể dùng trực tiếp làm type hint generic. Hai cách thoát nhanh: đổi sang các kiểu tương đương trong module typing, hoặc thêm from __future__ import annotations vào đầu file.
# Lỗi trên Python < 3.9
def get_scores(names: list[str]) -> dict[str, int]:
return {name: 0 for name in names}
# Cách 1: dùng module typing (hỗ trợ từ Python 3.5 trở lên)
from typing import List, Dict
def get_scores(names: List[str]) -> Dict[str, int]:
return {name: 0 for name in names}
# Cách 2: lazy annotations (hỗ trợ từ Python 3.7 trở lên)
from __future__ import annotations
def get_scores(names: list[str]) -> dict[str, int]:
return {name: 0 for name in names}
Nguyên Nhân Gây Ra Lỗi
PEP 585, ra mắt trong Python 3.9, đã bổ sung hỗ trợ generic gốc cho các kiểu dữ liệu tích hợp. Trước đó, list và dict không hỗ trợ toán tử subscript ([]) lúc runtime. Chỉ có các lớp wrapper trong module typing — List, Dict và các loại tương tự — mới biết cách xử lý điều này.
Mặc định, Python đánh giá các annotation ngay lúc import. Khi gặp list[int] trên interpreter cũ hơn, Python sẽ cố subscript kiểu list thô — và lập tức báo lỗi. Các trường hợp hay gặp:
- Signature hàm:
def foo(x: list[int]) - Annotation biến:
scores: dict[str, float] = {} - Định nghĩa field trong dataclass dùng cú pháp PEP 585
- Model Pydantic hoặc attrs chạy trên Python 3.8
Không chắc đang dùng Python phiên bản nào? Kiểm tra nhanh:
python --version
# Hoặc bên trong script:
import sys
print(sys.version_info) # ví dụ: sys.version_info(major=3, minor=8, micro=18, ...)
Các Cách Sửa Lỗi
Cách 1: Import từ module typing (an toàn nhất cho thư viện)
Thay các tên kiểu tích hợp bằng các tên viết hoa tương ứng từ typing. Dài dòng hơn, đúng — nhưng hoạt động từ Python 3.5 trở lên và tương thích tốt với mọi công cụ trong hệ sinh thái.
from typing import Dict, FrozenSet, List, Optional, Set, Tuple, Type
def process(items: List[int]) -> Tuple[int, ...]:
return tuple(items)
def lookup(mapping: Dict[str, List[str]]) -> Optional[str]:
return mapping.get("key", [None])[0]
Bảng tham khảo nhanh cho các kiểu hay dùng nhất:
# Python < 3.9 → Python 3.9+
List[int] → list[int]
Dict[str, int] → dict[str, int]
Tuple[int, ...] → tuple[int, ...]
Set[str] → set[str]
FrozenSet[str] → frozenset[str]
Type[MyClass] → type[MyClass]
Optional[str] → str | None (3.10+)
Cách 2: from future import annotations
Một dòng import, không cần thay đổi annotation nào. Dòng này khiến Python lưu tất cả annotation dưới dạng chuỗi thay vì đánh giá chúng lúc runtime — nhờ đó bạn có thể dùng cú pháp PEP 585 thoải mái trên Python 3.7+.
from __future__ import annotations
def merge(a: list[str], b: list[str]) -> dict[str, int]:
return {k: i for i, k in enumerate(a + b)}
Với các codebase lớn có hàng trăm file, đây là cách nhanh nhất — không cần tìm và thay thế khắp nơi trong các import. Một lưu ý: các thư viện đọc annotation lúc runtime (dependency injection của FastAPI, các serializer như cattrs) sẽ nhận chuỗi thay vì đối tượng kiểu thực, và có thể cần xử lý thêm.
Cách 3: Nâng cấp lên Python 3.9+
Nếu bạn kiểm soát được runtime, đây là hướng sạch sẽ nhất về lâu dài. Python 3.9 đã biến built-in generic thành công dân hạng nhất — không cần import, không cần workaround.
# Python 3.9+ — không cần import gì thêm
def summarize(data: list[dict[str, int]]) -> list[int]:
return [sum(d.values()) for d in data]
Thêm: field trong dataclass
Dataclass là một nguồn gây lỗi hay bị bỏ qua. Khác với annotation trong hàm, annotation của field được đánh giá ngay lúc định nghĩa class — nên lỗi xuất hiện khi import, không phải khi gọi hàm.
# Lỗi trên Python 3.8
from dataclasses import dataclass
@dataclass
class Report:
scores: dict[str, int] # TypeError ngay tại đây, lúc import
tags: list[str]
# Sửa: chỉ cần một dòng future import là xong
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Report:
scores: dict[str, int] # Ổn rồi
tags: list[str]
Kiểm Tra Sau Khi Sửa
Chạy thử module của bạn. Import thành công là xong:
python my_module.py
Muốn kiểm tra nhanh mà không chạy toàn bộ script:
python -c "from my_module import get_scores; print(get_scores(['alice', 'bob']))"
Muốn xác nhận annotation có hợp lệ không? Kiểm tra trực tiếp:
import inspect
import my_module
print(inspect.signature(my_module.get_scores))
# Output: (names: List[str]) -> Dict[str, int]

