Giao diện
Type Hinting Doanh nghiệp
Python có Dynamic Typing, nhưng code Production cần Static Analysis
Learning Outcomes
Sau khi hoàn thành trang này, bạn sẽ:
- 🎯 Hiểu tại sao type hints quan trọng trong production
- 🎯 Sử dụng thành thạo Python 3.12 type parameter syntax
- 🎯 Nắm vững TypeVarTuple, ParamSpec cho advanced generics
- 🎯 Cấu hình mypy/pyright cho project thực tế
Tại sao cần Type Hinting?
python
# ❌ KHÔNG CÓ TYPE: Đây là gì?
def xu_ly(data, config):
return data.get(config)
# ✅ CÓ TYPE: Rõ ràng như pha lê
def xu_ly(data: dict[str, Any], config: str) -> Any:
return data.get(config)Big Tech đều dùng Type Hinting
| Công ty | Công cụ | Bắt buộc |
|---|---|---|
| pytype | ✅ | |
| Meta | Pyre | ✅ |
| Microsoft | Pyright | ✅ |
| Dropbox | mypy | ✅ |
🚨 THỰC TẾ PHŨ PHÀNG
Nếu bạn viết Python mà không có type hints, bạn đang viết code của năm 2010.
Type Hints Cơ bản
Kiểu Nguyên thủy
python
# Biến
ten: str = "HPN"
tuoi: int = 28
luong: float = 50000.0
hoat_dong: bool = True
# Hàm
def chao(ten: str) -> str:
return f"Xin chào, {ten}"
def tinh_toan(x: int, y: int) -> float:
return x / y
# Trả về None
def ghi_log(tin_nhan: str) -> None:
print(tin_nhan)Kiểu Collection (Python 3.9+)
python
# ✅ Cú pháp hiện đại (Python 3.9+)
users: list[str] = ["Alice", "Bob"]
tuoi_map: dict[str, int] = {"Alice": 25, "Bob": 30}
toa_do: tuple[float, float] = (10.5, 20.3)
id_duy_nhat: set[int] = {1, 2, 3}
# ❌ Cú pháp cũ (Python 3.8 trở về)
from typing import List, Dict, Tuple, Set
users: List[str] = ["Alice", "Bob"]Kiểu Nâng cao
Optional - Có thể là None
python
from typing import Optional
def tim_user(user_id: int) -> Optional[dict]:
"""Trả về dict user hoặc None nếu không tìm thấy."""
user = db.get(user_id)
return user # Có thể là None
# Cú pháp Python 3.10+
def tim_user(user_id: int) -> dict | None:
return db.get(user_id)Union - Nhiều kiểu
python
from typing import Union
# Python 3.9 trở về
def phan_tich(gia_tri: Union[str, int]) -> str:
return str(gia_tri)
# Python 3.10+ (gọn hơn)
def phan_tich(gia_tri: str | int) -> str:
return str(gia_tri)Callable - Kiểu hàm
python
from typing import Callable
# Hàm nhận (int, int) và trả về int
def ap_dung_phep_toan(
x: int,
y: int,
phep_toan: Callable[[int, int], int]
) -> int:
return phep_toan(x, y)
# Sử dụng
ket_qua = ap_dung_phep_toan(5, 3, lambda a, b: a + b)TypeVar - Generics
python
from typing import TypeVar, Sequence
T = TypeVar('T')
def phan_tu_dau(items: Sequence[T]) -> T:
"""Trả về phần tử đầu, giữ nguyên kiểu."""
return items[0]
# Type checker biết:
phan_tu_dau([1, 2, 3]) # Trả về int
phan_tu_dau(["a", "b"]) # Trả về strType Aliases
python
from typing import TypeAlias
# Alias đơn giản
UserId: TypeAlias = int
Username: TypeAlias = str
# Alias phức tạp
UserDict: TypeAlias = dict[str, str | int | None]
Callback: TypeAlias = Callable[[str], None]
# Sử dụng
def lay_user(user_id: UserId) -> UserDict:
return {"id": user_id, "ten": "HPN"}TypedDict - Dict có cấu trúc
python
from typing import TypedDict, NotRequired
class UserProfile(TypedDict):
id: int
ten: str
email: str
tuoi: NotRequired[int] # Trường tùy chọn
def tao_user(data: UserProfile) -> None:
print(data["ten"]) # Type checker biết đây là str
# ✅ Hợp lệ
tao_user({"id": 1, "ten": "HPN", "email": "r@x.com"})
# ❌ Lỗi type: thiếu 'email'
tao_user({"id": 1, "ten": "HPN"})MyPy - Công cụ Kiểm tra Type Tĩnh
Cài đặt
bash
pip install mypyCấu hình (mypy.ini)
ini
[mypy]
python_version = 3.11
strict = true
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
# Ghi đè theo module
[mypy-tests.*]
disallow_untyped_defs = falseChạy MyPy
bash
# Kiểm tra một file
mypy app.py
# Kiểm tra toàn bộ project
mypy src/
# Với file cấu hình
mypy --config-file mypy.ini src/Lỗi MyPy Thường gặp
python
# Lỗi: Thiếu kiểu trả về
def chao(ten): # ❌ error: Function is missing a return type
return f"Xin chào {ten}"
# Sửa:
def chao(ten: str) -> str: # ✅
return f"Xin chào {ten}"
# Lỗi: Kiểu không tương thích
def xu_ly(x: int) -> None:
pass
xu_ly("xin_chao") # ❌ error: Argument 1 has incompatible type "str"
# Lỗi: Truy cập Optional
def lay_ten(user: dict | None) -> str:
return user["ten"] # ❌ error: Value of type "dict | None" is not indexable
# Sửa: Kiểm tra None trước
def lay_ten(user: dict | None) -> str:
if user is None:
return "Không rõ"
return user["ten"] # ✅Python 3.12 Type Parameter Syntax
Python 3.12 giới thiệu cú pháp mới cho generics, đơn giản và trực quan hơn.
Cú pháp Cũ vs Mới
python
# ❌ CŨ (Python 3.11 trở về): Phải import và khai báo TypeVar
from typing import TypeVar, Generic
T = TypeVar('T')
K = TypeVar('K')
V = TypeVar('V')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
# ✅ MỚI (Python 3.12+): Type parameter syntax
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()Generic Functions (Python 3.12+)
python
# ❌ CŨ: Khai báo TypeVar riêng
from typing import TypeVar, Sequence
T = TypeVar('T')
def first(items: Sequence[T]) -> T:
return items[0]
# ✅ MỚI: Inline type parameter
def first[T](items: Sequence[T]) -> T:
return items[0]
# Multiple type parameters
def swap[K, V](d: dict[K, V]) -> dict[V, K]:
return {v: k for k, v in d.items()}Bounded Type Parameters
python
# ❌ CŨ: TypeVar với bound
from typing import TypeVar
Numeric = TypeVar('Numeric', bound=int | float)
def double(x: Numeric) -> Numeric:
return x * 2
# ✅ MỚI: Inline bound
def double[T: (int | float)](x: T) -> T:
return x * 2
# Constrained types (chỉ cho phép một số types cụ thể)
def process[T: (str, bytes)](data: T) -> T:
return data.upper() if isinstance(data, str) else data.upper()Type Aliases (Python 3.12+)
python
# ❌ CŨ: TypeAlias
from typing import TypeAlias
Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[list[float]]
# ✅ MỚI: type statement
type Vector = list[float]
type Matrix = list[list[float]]
# Generic type alias
type ListOrSet[T] = list[T] | set[T]
type Callback[T] = Callable[[T], None]
# Sử dụng
def process(data: ListOrSet[int]) -> None:
for item in data:
print(item)TypeVarTuple - Variadic Generics
TypeVarTuple cho phép định nghĩa số lượng type parameters không cố định.
Use Case: Tuple với độ dài bất kỳ
python
from typing import TypeVarTuple, Unpack
Ts = TypeVarTuple('Ts')
# Function nhận tuple với bất kỳ số lượng types
def head_and_tail[*Ts](t: tuple[int, *Ts]) -> tuple[int, tuple[*Ts]]:
"""Tách head (int) và tail (rest) của tuple."""
return t[0], t[1:]
# Sử dụng
result = head_and_tail((1, "hello", 3.14))
# result: tuple[int, tuple[str, float]]Use Case: Decorator giữ nguyên signature
python
from typing import TypeVarTuple, Callable, Unpack
Ts = TypeVarTuple('Ts')
def logged[*Ts, R](func: Callable[[*Ts], R]) -> Callable[[*Ts], R]:
"""Decorator log function calls, giữ nguyên type signature."""
def wrapper(*args: *Ts) -> R:
print(f"Calling {func.__name__} with {args}")
return func(*args)
return wrapper
@logged
def add(a: int, b: int) -> int:
return a + b
# Type checker biết: add(int, int) -> intUse Case: Generic Array Operations
python
from typing import TypeVarTuple
Shape = TypeVarTuple('Shape')
class NDArray[*Shape]:
"""N-dimensional array với shape được track bởi type system."""
def __init__(self, data: list) -> None:
self.data = data
def reshape[*NewShape](self, *new_shape: *NewShape) -> "NDArray[*NewShape]":
# Implementation
...
# Type checker tracks shape
arr: NDArray[int, int, int] = NDArray([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
# Shape: 2x2x2ParamSpec - Callable Generics
ParamSpec cho phép capture và forward parameter types của Callable.
Problem: Decorator mất type information
python
from typing import Callable, Any
# ❌ PROBLEM: Decorator làm mất type info
def timer(func: Callable[..., Any]) -> Callable[..., Any]:
def wrapper(*args: Any, **kwargs: Any) -> Any:
import time
start = time.time()
result = func(*args, **kwargs)
print(f"Took {time.time() - start:.2f}s")
return result
return wrapper
@timer
def greet(name: str, times: int = 1) -> str:
return f"Hello {name}!" * times
# Type checker: greet(*args, **kwargs) -> Any ❌ Lost signature!Solution: ParamSpec
python
from typing import ParamSpec, Callable, TypeVar
P = ParamSpec('P')
R = TypeVar('R')
# ✅ SOLUTION: ParamSpec preserves signature
def timer(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
import time
start = time.time()
result = func(*args, **kwargs)
print(f"Took {time.time() - start:.2f}s")
return result
return wrapper
@timer
def greet(name: str, times: int = 1) -> str:
return f"Hello {name}!" * times
# Type checker: greet(name: str, times: int = 1) -> str ✅ Preserved!Python 3.12 Syntax
python
# ✅ Python 3.12+: Cleaner syntax
def timer[**P, R](func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
import time
start = time.time()
result = func(*args, **kwargs)
print(f"Took {time.time() - start:.2f}s")
return result
return wrapperUse Case: Dependency Injection
python
from typing import ParamSpec, Callable, TypeVar
P = ParamSpec('P')
R = TypeVar('R')
def inject_db[**P, R](
func: Callable[..., R]
) -> Callable[P, R]:
"""Inject database connection vào function."""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
db = get_database_connection()
return func(db, *args, **kwargs)
return wrapper
@inject_db
def get_user(db: Database, user_id: int) -> User:
return db.query(User).get(user_id)
# Caller không cần pass db
user = get_user(user_id=123) # db được inject tự độngCấu hình MyPy Production
File mypy.ini Đầy đủ
ini
[mypy]
# Python version
python_version = 3.12
# Strict mode (recommended for new projects)
strict = true
# Hoặc enable từng option
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
strict_equality = true
strict_concatenate = true
# Error formatting
show_error_codes = true
show_column_numbers = true
pretty = true
# Performance
cache_dir = .mypy_cache
incremental = true
# Plugins (nếu dùng)
plugins = [
"pydantic.mypy",
"sqlalchemy.ext.mypy.plugin"
]
# === Per-module overrides ===
# Tests có thể loose hơn
[mypy-tests.*]
disallow_untyped_defs = false
disallow_incomplete_defs = false
# Third-party libraries không có stubs
[mypy-some_untyped_library.*]
ignore_missing_imports = true
# Legacy code đang migrate
[mypy-legacy.*]
ignore_errors = trueFile pyproject.toml (Alternative)
toml
[tool.mypy]
python_version = "3.12"
strict = true
show_error_codes = true
pretty = true
plugins = [
"pydantic.mypy",
]
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
[[tool.mypy.overrides]]
module = "some_untyped_library.*"
ignore_missing_imports = trueCấu hình Pyright
Pyright (Microsoft) nhanh hơn mypy và tích hợp tốt với VS Code.
File pyrightconfig.json
json
{
"include": ["src"],
"exclude": ["**/node_modules", "**/__pycache__", ".venv"],
"pythonVersion": "3.12",
"pythonPlatform": "Linux",
"typeCheckingMode": "strict",
"reportMissingImports": true,
"reportMissingTypeStubs": false,
"reportUnusedImport": true,
"reportUnusedVariable": true,
"reportDuplicateImport": true,
"reportPrivateUsage": true,
"reportConstantRedefinition": true,
"reportIncompatibleMethodOverride": true,
"reportIncompatibleVariableOverride": true,
"reportInconsistentConstructor": true,
"reportUntypedFunctionDecorator": true,
"reportUntypedClassDecorator": true,
"reportUntypedBaseClass": true,
"reportUntypedNamedTuple": true,
"reportCallInDefaultInitializer": true,
"reportUnnecessaryIsInstance": true,
"reportUnnecessaryCast": true,
"reportUnnecessaryComparison": true,
"reportAssertAlwaysTrue": true,
"executionEnvironments": [
{
"root": "src",
"pythonVersion": "3.12",
"extraPaths": ["src"]
},
{
"root": "tests",
"pythonVersion": "3.12",
"extraPaths": ["src"]
}
]
}VS Code Settings
json
{
"python.analysis.typeCheckingMode": "strict",
"python.analysis.diagnosticMode": "workspace",
"python.analysis.autoImportCompletions": true,
"python.analysis.inlayHints.functionReturnTypes": true,
"python.analysis.inlayHints.variableTypes": true
}Production Pitfalls
Pitfall 1: Any Là Escape Hatch
python
from typing import Any
# ❌ BAD: Any vô hiệu hóa type checking
def process(data: Any) -> Any:
return data.foo.bar.baz() # No error, but runtime crash!
# ✅ GOOD: Dùng Union hoặc Protocol
from typing import Protocol
class HasFoo(Protocol):
foo: "HasBar"
class HasBar(Protocol):
bar: "HasBaz"
class HasBaz(Protocol):
def baz(self) -> str: ...
def process(data: HasFoo) -> str:
return data.foo.bar.baz() # Type safe!Pitfall 2: Covariance vs Contravariance
python
# ❌ BUG: List là invariant
def process_animals(animals: list[Animal]) -> None:
animals.append(Cat()) # Có thể thêm Cat
dogs: list[Dog] = [Dog(), Dog()]
process_animals(dogs) # Type error! list[Dog] != list[Animal]
# Nếu cho phép, dogs sẽ chứa Cat!
# ✅ SOLUTION: Dùng Sequence (covariant, read-only)
from typing import Sequence
def process_animals(animals: Sequence[Animal]) -> None:
for animal in animals:
animal.speak() # OK, chỉ đọc
dogs: list[Dog] = [Dog(), Dog()]
process_animals(dogs) # OK! Sequence[Dog] compatible với Sequence[Animal]Pitfall 3: Forward References
python
# ❌ ERROR: Class chưa được định nghĩa
class Node:
def __init__(self, child: Node) -> None: # NameError!
self.child = child
# ✅ SOLUTION 1: String annotation
class Node:
def __init__(self, child: "Node") -> None:
self.child = child
# ✅ SOLUTION 2: from __future__ import annotations (Python 3.7+)
from __future__ import annotations
class Node:
def __init__(self, child: Node) -> None: # OK!
self.child = childPitfall 4: Type Narrowing Không Hoạt Động
python
# ❌ Type checker không hiểu custom check
def is_string(x: object) -> bool:
return isinstance(x, str)
def process(x: str | int) -> None:
if is_string(x):
print(x.upper()) # Error: x is still str | int
# ✅ SOLUTION: TypeGuard
from typing import TypeGuard
def is_string(x: object) -> TypeGuard[str]:
return isinstance(x, str)
def process(x: str | int) -> None:
if is_string(x):
print(x.upper()) # OK! x is narrowed to strCross-links
- Protocols & ABCs - Structural typing với Protocol
- Decorators - Typed decorators với ParamSpec
- Data Structures - Generic collections
Cấu hình Production của HPN
💡 MẸO CỦA HPN
Backend code của Penalgo sử dụng strict typing. Tải HPN Starter Kit để xem cấu hình mypy production của chúng tôi với:
- Pre-commit hooks tự động kiểm tra type
- Tích hợp CI/CD với GitHub Actions
- Cấu hình VS Code cho real-time type checking
✦
✧
✦
PREMIUM
HPN Interview Kit
Bộ câu hỏi phỏng vấn Big Tech
- ✓ 200+ câu hỏi thực tế từ FAANG
- ✓ Giải thích chi tiết bằng Tiếng Việt
- ✓ Cập nhật liên tục 2025
Bảng Tóm tắt
python
# === KIỂU CƠ BẢN ===
x: int = 1
y: float = 1.0
s: str = "xin chào"
b: bool = True
none_val: None = None
# === COLLECTIONS (Python 3.9+) ===
items: list[int] = [1, 2, 3]
mapping: dict[str, int] = {"a": 1}
toa_do: tuple[int, int] = (10, 20)
ids: set[int] = {1, 2, 3}
# === CHỮ KÝ HÀM ===
def func(a: int, b: str = "mac_dinh") -> bool:
return True
# === OPTIONAL / UNION ===
from typing import Optional
co_the: Optional[str] = None # str | None
# Python 3.10+
co_the: str | None = None
mot_trong_hai: int | str = 42
# === CALLABLE ===
from typing import Callable
handler: Callable[[int, str], bool]
# === GENERICS ===
from typing import TypeVar
T = TypeVar('T')
def dau_tien(items: list[T]) -> T: ...
# === TYPE ALIAS ===
UserId = int # Đơn giản
from typing import TypeAlias
UserDict: TypeAlias = dict[str, Any]