Skip to content

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 tyCông cụBắt buộc
Googlepytype
MetaPyre
MicrosoftPyright
Dropboxmypy

🚨 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ề str

Type 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 mypy

Cấ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 = false

Chạ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) -> int

Use 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: 2x2x2

ParamSpec - 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 wrapper

Use 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ự động

Cấ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 = true

File 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 = true

Cấ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 = child

Pitfall 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 str


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
Sở hữu ngay99k

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]