Giao diện
Production Gotchas — Những Bẫy Python trong Production
IMPORTANT
Trang này là appendix — bạn có thể đọc bất kỳ lúc nào, không cần hoàn thành tuần tự các bài trước. Hãy bookmark lại và quay lại mỗi khi gặp bug "ma" trong production.
🎯 Mục tiêu
Trang này là bộ sưu tập những bẫy Python hay gặp trong production. Mỗi gotcha đi kèm ví dụ lỗi, giải thích tại sao, và cách fix.
Sau khi đọc xong, bạn sẽ:
- Nhận diện được 8 anti-patterns phổ biến nhất trong Python production code
- Hiểu tại sao chúng xảy ra ở mức ngôn ngữ (không chỉ biết fix mà hiểu gốc rễ)
- Có ngay code template để thay thế từng pattern lỗi
- Biết cách phát hiện chúng trong code review trước khi chúng lên production
Gotcha #1 — Mutable Default Arguments
⚠️ Bẫy kinh điển nhất của Python
Đây là gotcha số 1 mà mọi Python developer đều dính ít nhất một lần. Nó trông hoàn toàn hợp lý nhưng hành vi thì ngược lại trực giác.
❌ Code trông đúng nhưng sai hoàn toàn
python
# ❌ Classic trap — đừng bao giờ viết thế này
def add_item(item: str, items: list[str] = []) -> list[str]:
items.append(item)
return items
# Thử chạy:
print(add_item("a")) # ["a"] — ok, đúng rồi
print(add_item("b")) # ["a", "b"] — SURPRISE! Sao lại có "a" ở đây?
print(add_item("c")) # ["a", "b", "c"] — List cứ lớn dần!🔍 Chuyện gì thực sự xảy ra?
Default arguments trong Python được đánh giá MỘT LẦN DUY NHẤT tại thời điểm định nghĩa hàm (def), không phải mỗi lần gọi hàm.
Nghĩa là cái [] được tạo ra một lần khi Python parse hàm, và mọi lời gọi hàm đều dùng chung cùng một object list đó. Bạn có thể kiểm chứng:
python
def add_item(item: str, items: list[str] = []) -> list[str]:
print(f"id(items) = {id(items)}") # Luôn cùng một id!
items.append(item)
return items
add_item("a") # id(items) = 140234567890
add_item("b") # id(items) = 140234567890 ← CÙNG object!✅ Cách fix đúng
python
# ✅ Dùng None làm sentinel, tạo list mới trong body
def add_item(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return items
# Bây giờ mỗi lần gọi đều có list riêng
print(add_item("a")) # ["a"]
print(add_item("b")) # ["b"] — Đúng!💥 Khi nào nó cắn bạn trong production?
- API endpoints tích lũy state giữa các requests — một endpoint trả về data của request trước
- Background task queues phình ra vì list tham số không bao giờ reset
- Unit tests flaky vì test chạy sau "kế thừa" state từ test chạy trước
- Celery workers chia sẻ mutable default giữa các task executions
Gotcha #2 — Late Binding Closures trong Loops
⚠️ Closures bắt biến, không bắt giá trị
Gotcha này đặc biệt nguy hiểm vì nó chỉ lộ ra khi callbacks thực sự chạy — thường là rất lâu sau khi bạn viết code.
❌ Tất cả callbacks đều in ra 4!
python
# ❌ Mong đợi: 0, 1, 2, 3, 4 — Thực tế: 4, 4, 4, 4, 4
callbacks = []
for i in range(5):
callbacks.append(lambda: print(i))
for cb in callbacks:
cb() # 4, 4, 4, 4, 4 — KHÔNG phải 0, 1, 2, 3, 4!🔍 Chuyện gì thực sự xảy ra?
Closures trong Python bắt biến (i), không phải bắt giá trị của biến tại thời điểm tạo closure. Khi callback chạy, nó tìm giá trị hiện tại của i — nhưng lúc đó vòng for đã kết thúc và i == 4.
python
# Cách Python "nhìn" code trên:
# Tất cả 5 lambda đều chứa CÙNG MỘT tham chiếu đến biến i
# Khi vòng for kết thúc, i = 4
# → Mọi lambda khi gọi đều đọc i = 4✅ Cách fix đúng
python
# ✅ Fix 1: Capture giá trị bằng default argument
callbacks = []
for i in range(5):
callbacks.append(lambda i=i: print(i)) # i=i "chụp" giá trị tại lúc tạo
for cb in callbacks:
cb() # 0, 1, 2, 3, 4 — Đúng!
# ✅ Fix 2: Dùng functools.partial (rõ ràng hơn)
from functools import partial
callbacks = []
for i in range(5):
callbacks.append(partial(print, i))
for cb in callbacks:
cb() # 0, 1, 2, 3, 4 — Đúng!💥 Khi nào nó cắn bạn trong production?
- Event handlers trong GUI/web framework — tất cả button gọi cùng một action
- Callback registrations trong message queues — consumer xử lý sai message
- Scheduled tasks (cron, APScheduler) — tất cả job đều trỏ đến config cuối cùng
- Retry logic — retry với sai parameters vì closure bắt biến đã thay đổi
Gotcha #3 — Import-Time Side Effects
⚠️ Code chạy lúc import, không phải lúc bạn muốn
Python thực thi toàn bộ code ở module-level khi bạn import. Nếu code đó có side effects, bạn sẽ có một ngày rất tồi tệ.
❌ Code lỗi — Side effects tại thời điểm import
python
# ❌ config.py — Crash nếu env var chưa set lúc import
import os
DATABASE_URL = os.environ["DATABASE_URL"] # 💀 KeyError nếu chưa set!
# ❌ models.py — Chạy query tại thời điểm import!
from database import db
ADMIN_USERS = db.query("SELECT * FROM users WHERE role='admin'") # 💀 Chạy ngay lúc import!
# ❌ utils.py — HTTP call tại import time
import requests
FEATURE_FLAGS = requests.get("https://api.example.com/flags").json() # 💀 Import = gọi API!🔍 Chuyện gì thực sự xảy ra?
Khi Python gặp import config, nó thực thi toàn bộ code trong config.py từ trên xuống dưới. Mọi assignment, function call, hay expression ở module-level đều chạy ngay.
Hậu quả:
- Import sẽ fail nếu environment chưa sẵn sàng (chưa set env vars, DB chưa start)
- Test không chạy được vì import chain kéo theo DB connections
- Circular imports trở nên impossible to debug
✅ Cách fix đúng
python
# ✅ Cách 1: Lazy initialization với function
import os
DATABASE_URL: str | None = None
def get_database_url() -> str:
global DATABASE_URL
if DATABASE_URL is None:
DATABASE_URL = os.environ["DATABASE_URL"]
return DATABASE_URL
# ✅ Cách 2: Settings class với dataclass (production pattern)
from dataclasses import dataclass, field
import os
@dataclass
class Settings:
database_url: str = field(
default_factory=lambda: os.environ.get("DATABASE_URL", "")
)
debug: bool = field(
default_factory=lambda: os.environ.get("DEBUG", "false").lower() == "true"
)
# Chỉ tạo instance khi cần, không phải lúc import
_settings: Settings | None = None
def get_settings() -> Settings:
global _settings
if _settings is None:
_settings = Settings()
return _settings💥 Khi nào nó cắn bạn trong production?
- Tests fail vì environment chưa setup xong mà module đã import
- CI pipeline breaks —
import appcrash trước khi test runner chạy - Circular import chains —
ImportErrorhoặcAttributeErrorbí ẩn - Docker container crash — env vars chưa inject mà entrypoint đã import modules
- Slow startup — import chain kéo theo hàng chục HTTP calls/DB queries
Gotcha #4 — Datetime Timezone: Naive vs Aware
⚠️ datetime.now() là bom hẹn giờ
Nếu bạn đang dùng datetime.now() hoặc datetime.utcnow() mà không có timezone info — bạn đang tạo bug chỉ lộ ra khi server và user ở khác timezone.
❌ Code trông đúng nhưng là bom nổ chậm
python
from datetime import datetime
# ❌ Naive datetime — không ai biết timezone là gì!
now = datetime.now() # Timezone nào? Không biết!
print(now) # 2024-01-15 14:30:00 — UTC? Local? Ai biết?
# ❌ utcnow() cũng naive! Chỉ là giá trị UTC nhưng KHÔNG đánh dấu là UTC
utc_now = datetime.utcnow()
print(utc_now.tzinfo) # None! ← Vẫn là naive datetime
# ❌ So sánh naive với aware → TypeError!
from datetime import timezone
aware = datetime.now(timezone.utc)
# now < aware → TypeError: can't compare offset-naive and offset-aware datetimes🔍 Chuyện gì thực sự xảy ra?
Python có 2 loại datetime:
- Naive: Không có thông tin timezone (
tzinfo is None). Python không biết đây là giờ gì. - Aware: Có timezone gắn kèm (
tzinfo is not None). Rõ ràng và an toàn.
datetime.now() và datetime.utcnow() đều trả về naive datetime. Bạn không thể so sánh, trừ, hay kết hợp naive với aware — Python sẽ raise TypeError.
✅ Cách fix đúng
python
from datetime import datetime, timezone
# ✅ Luôn dùng timezone-aware datetimes
now_utc = datetime.now(timezone.utc)
print(now_utc) # 2024-01-15 07:30:00+00:00 — Rõ ràng là UTC!
# ✅ Với timezone khác — dùng zoneinfo (Python 3.9+)
from zoneinfo import ZoneInfo
vietnam_time = datetime.now(ZoneInfo("Asia/Ho_Chi_Minh"))
print(vietnam_time) # 2024-01-15 14:30:00+07:00 — Rõ ràng là giờ VN!
# ✅ Convert giữa các timezone
utc_time = datetime.now(timezone.utc)
vn_time = utc_time.astimezone(ZoneInfo("Asia/Ho_Chi_Minh"))
# ✅ Lưu vào database: LUÔN dùng UTC
created_at = datetime.now(timezone.utc) # Lưu UTC
# Khi hiển thị cho user ở VN:
display_time = created_at.astimezone(ZoneInfo("Asia/Ho_Chi_Minh"))TIP
Quy tắc vàng: LUÔN lưu UTC trong database. Chỉ convert sang timezone local ở tầng hiển thị (display layer). Đừng bao giờ lưu local time vào DB.
💥 Khi nào nó cắn bạn trong production?
- Scheduled tasks chạy sai giờ — cron job chạy lúc 2 AM UTC thay vì 2 AM giờ VN
- Billing calculations sai lệch vài giờ — tính phí sai ngày vì timezone
- Logs với timestamps mập mờ — không biết log entry này là giờ server hay giờ UTC
- Distributed systems — 2 server ở 2 timezone tạo ra ordering bugs
- Daylight Saving Time — giờ nhảy 1 tiếng gây duplicate/missing records
Gotcha #5 — Silent Exception Swallowing: Bare except
🚨 Anti-pattern tồi tệ nhất trong Python
except: pass là cách giấu bug hiệu quả nhất. Bug vẫn ở đó, bạn chỉ không thấy nó — cho đến khi production cháy.
❌ Code "xử lý lỗi" mà thực ra là giấu lỗi
python
# ❌ Anti-pattern TỒI TỆ NHẤT — bắt mọi thứ, nuốt hết
try:
result = process_payment(order)
except: # 💀 Bắt EVERYTHING: KeyboardInterrupt, SystemExit, MemoryError
pass # 💀 Nuốt lỗi im lặng — không log, không raise, không thông báo
# ❌ Tệ gần bằng — bắt Exception nhưng vẫn nuốt
try:
result = process_payment(order)
except Exception:
pass # 💀 Payment fail nhưng không ai biết!
# ❌ Biến thể nguy hiểm — log nhưng tiếp tục như không có gì
try:
result = process_payment(order)
except Exception as e:
print(f"Error: {e}") # In ra stdout mà không ai đọc
result = None # 💀 Trả về None, caller không biết payment failed🔍 Chuyện gì thực sự xảy ra?
except:(bare except) bắt mọi exception, kể cảKeyboardInterrupt(Ctrl+C),SystemExit(sys.exit()),GeneratorExit. Bạn thậm chí không thể dừng chương trình bằng Ctrl+C!except Exception:tốt hơn một chút (không bắtKeyboardInterrupt/SystemExit) nhưngpasssau đó vẫn giấu mọi lỗi- Kết quả: bugs tích tụ âm thầm, data corruption không bị phát hiện, debugging trở nên bất khả thi
✅ Cách fix đúng
python
import logging
logger = logging.getLogger(__name__)
# ✅ Bắt exception CỤ THỂ, log LUÔN, handle hợp lý
try:
result = process_payment(order)
except PaymentGatewayError as e:
logger.error(
"Payment gateway failed for order %s: %s",
order.id, e,
exc_info=True, # Include full traceback trong log
)
raise OrderProcessingError(f"Payment failed: {e}") from e
except ValidationError as e:
logger.warning(
"Invalid payment data for order %s: %s",
order.id, e,
)
return PaymentResult(success=False, error=str(e))
except Exception as e:
# Catch-all cuối cùng — PHẢI log và re-raise
logger.exception("Unexpected error processing order %s", order.id)
raise # Re-raise để caller biết có lỗi!💥 Khi nào nó cắn bạn trong production?
- Tiền bị trừ nhưng order không tạo — payment processed, exception swallowed, order creation skipped
- Data corruption âm thầm diễn ra hàng tuần mà không ai hay — exceptions bị nuốt
- Debug bất khả thi — biết có bug nhưng không có log, không có traceback, không có manh mối
- Hệ thống "chạy tốt" nhưng data sai — metrics đẹp vì errors không bao giờ được count
Gotcha #6 — Circular Imports
⚠️ ImportError bí ẩn lúc 5 giờ chiều thứ Sáu
Circular imports xảy ra khi module A import module B, và module B import lại module A. Python sẽ raise ImportError hoặc tệ hơn — trả về module chưa load xong.
❌ Vòng lặp import chết
python
# ❌ models.py
from services import UserService # → import services.py
class User:
def get_service(self) -> UserService:
return UserService(self)
# ❌ services.py
from models import User # → import models.py → đang import services.py → 💀 ImportError!
class UserService:
def get_user(self, user_id: int) -> User:
...🔍 Chuyện gì thực sự xảy ra?
- Python bắt đầu load
models.py - Gặp
from services import UserService→ chuyển sang loadservices.py - Trong
services.py, gặpfrom models import User→ quay lạimodels.py - Nhưng
models.pychưa load xong! ClassUserchưa được define - Kết quả:
ImportError: cannot import name 'User'
✅ Cách fix đúng
python
# ✅ Cách 1: TYPE_CHECKING — import chỉ cho type checker
from __future__ import annotations # Bắt buộc! Biến annotations thành strings
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models import User # Chỉ chạy khi type-checking, KHÔNG chạy runtime
class UserService:
def get_user(self, user_id: int) -> "User": # String annotation
from models import User # Lazy import ở runtime khi thực sự cần
return User.from_db(user_id)python
# ✅ Cách 2: Lazy import trong function body
class UserService:
def get_user(self, user_id: int) -> "User":
from models import User # Import tại thời điểm gọi, không phải module-level
return User.from_db(user_id)python
# ✅ Cách 3: Tách shared types ra module riêng
# types_.py — Module "lá", không import gì nặng
from dataclasses import dataclass
@dataclass
class UserDTO:
id: int
name: str
# models.py
from types_ import UserDTO # Không circular
# services.py
from types_ import UserDTO # Không circular💥 Khi nào nó cắn bạn trong production?
ImportErrorxuất hiện bí ẩn sau khi refactor- Test isolation issues — test pass riêng lẻ nhưng fail khi chạy cùng suite
AttributeErrorkỳ lạ — module load xong một nửa, class chưa define- Deployment fails — CI chạy import thứ tự khác local → lỗi chỉ trên CI
Gotcha #7 — is vs ==: Identity vs Equality
⚠️ is "chạy đúng" rồi đột nhiên sai — vì CPython caching
is kiểm tra identity (cùng object trong memory), == kiểm tra equality (cùng giá trị). Nhầm lẫn hai cái này tạo ra bugs "works on my machine".
❌ Code hoạt động "tình cờ" rồi fail
python
# ❌ "Works" do CPython implementation detail
a = 256
b = 256
print(a is b) # True ← CPython cache integers -5 đến 256
a = 257
b = 257
print(a is b) # False! ← Ngoài cache range, hai object khác nhau
# ❌ Với strings — cũng unreliable
x = "hello"
y = "hello"
print(x is y) # True ← String interning (CPython optimization)
x = "hello world!"
y = "hello world!"
print(x is y) # Có thể True hoặc False! ← Phụ thuộc implementation
# ❌ ĐỪNG BAO GIỜ làm thế này
if user.role is "admin": # 💀 Có thể work, có thể fail — RANDOM
grant_access()🔍 Chuyện gì thực sự xảy ra?
isso sánh id() của hai objects — nghĩa là chúng phải là cùng một object trong memory==gọi method__eq__()— so sánh giá trị- CPython cache small integers (-5 đến 256) và một số strings →
iscó vẻ "work" nhưng chỉ là trùng hợp - Trên PyPy, Jython, hay thậm chí CPython version khác → kết quả
ishoàn toàn khác
✅ Quy tắc dùng đúng
python
# ✅ Dùng `is` CHỈ cho singletons
if value is None: # ✅ None là singleton
...
if flag is True: # ✅ True là singleton (nhưng ưu tiên `if flag:`)
...
if flag is False: # ✅ False là singleton (nhưng ưu tiên `if not flag:`)
...
# ✅ Dùng `==` cho MỌI so sánh giá trị
if user.role == "admin": # ✅ So sánh giá trị string
grant_access()
if count == 257: # ✅ So sánh giá trị integer
process()
if response.status == 200: # ✅ So sánh giá trị
handle_success()💥 Khi nào nó cắn bạn trong production?
- "Works on my machine" — code pass trên dev (CPython 3.11) nhưng fail trên server (PyPy hoặc CPython version khác)
- Intermittent failures —
issometimes True, sometimes False cho cùng input - Security bugs —
if token is "admin_token"bypass authentication đôi khi - Sau upgrade Python — internal caching behavior thay đổi giữa versions
Gotcha #8 — Memory Leaks: Forgotten References
⚠️ Python có garbage collector, nhưng vẫn leak memory được
"Python tự quản lý memory" không có nghĩa là bạn không thể leak. Global caches, event listeners, và circular references đều gây leak.
❌ Cache global phình vô hạn
python
from typing import Any
# ❌ Cache không bao giờ evict — phình mãi cho đến OOM
_cache: dict[str, Any] = {}
def get_user_profile(user_id: str) -> dict:
if user_id not in _cache:
_cache[user_id] = fetch_from_db(user_id) # Thêm vào nhưng không bao giờ xóa!
return _cache[user_id]
# Sau 100,000 unique users → hàng trăm MB trong memory
# Sau 1,000,000 users → OOMKilled by Kubernetespython
# ❌ Event listeners không bao giờ cleanup
class EventBus:
def __init__(self):
self._listeners: list[callable] = []
def subscribe(self, callback: callable) -> None:
self._listeners.append(callback) # Thêm nhưng không có unsubscribe!
def emit(self, event: str) -> None:
for listener in self._listeners:
listener(event)
# Mỗi request tạo lambda mới, subscribe, nhưng không bao giờ unsubscribe
# → _listeners phình vô hạn🔍 Chuyện gì thực sự xảy ra?
Python garbage collector dọn objects khi không còn reference nào trỏ đến chúng. Nhưng nếu bạn giữ reference trong global dict, list, hoặc class attribute — GC không thể dọn vì object vẫn "reachable".
Các nguồn leak phổ biến:
- Global caches không có eviction policy
- Event listeners / callbacks không unsubscribe
- File handles / DB connections không close
- Circular references mà GC không phát hiện được (khi có
__del__)
✅ Cách fix đúng
python
from functools import lru_cache
# ✅ Bounded cache với LRU eviction
@lru_cache(maxsize=1000) # Tối đa 1000 entries, tự evict cũ nhất
def get_user_profile(user_id: str) -> dict:
return fetch_from_db(user_id)
# Khi cần clear cache:
get_user_profile.cache_clear()python
# ✅ TTL cache — tự hết hạn sau N giây
from cachetools import TTLCache
_cache: TTLCache = TTLCache(maxsize=1000, ttl=300) # Max 1000, expire sau 5 phút
def get_user_profile(user_id: str) -> dict:
if user_id not in _cache:
_cache[user_id] = fetch_from_db(user_id)
return _cache[user_id]python
# ✅ Context manager cho resources — đảm bảo cleanup
from contextlib import contextmanager
@contextmanager
def managed_connection():
conn = create_db_connection()
try:
yield conn
finally:
conn.close() # LUÔN close, kể cả khi có exception
# Sử dụng:
with managed_connection() as conn:
result = conn.query("SELECT * FROM users")
# conn đã được close ở đâypython
# ✅ weakref cho caches không ngăn GC
import weakref
_cache: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
def get_or_create_user(user_id: str) -> User:
user = _cache.get(user_id)
if user is None:
user = User.from_db(user_id)
_cache[user_id] = user # GC vẫn có thể dọn nếu không ai khác reference
return user💥 Khi nào nó cắn bạn trong production?
- OOMKilled — Kubernetes kill pod vì memory vượt limit sau vài giờ/ngày chạy
- Slow memory creep — Memory usage tăng 1% mỗi giờ, chỉ phát hiện khi alert fire
- File descriptor exhaustion —
Too many open filesvì file handles không close - DB connection pool exhausted — connections leak ra ngoài pool, không return
- "Restart fixes it" — memory sạch sau restart, leak lại bắt đầu
🧪 Bài tập nhanh: Spot the Bug
🐛 Spot-the-Bug — 🐛 Tìm gotcha trong mỗi đoạn code
Với mỗi đoạn code dưới đây, xác định đây là gotcha nào và cách fix.
Snippet 1
python
def create_report(title: str, sections: list[str] = []) -> dict:
sections.append("summary")
return {"title": title, "sections": sections}👉 Xem đáp án
Gotcha #1 — Mutable Default Argument. Mỗi lần gọi create_report, "summary" lại được thêm vào cùng list. Report thứ 3 sẽ có 3 cái "summary".
Fix: sections: list[str] | None = None, rồi if sections is None: sections = [] trong body.
Snippet 2
python
from datetime import datetime
def is_expired(expiry_date: datetime) -> bool:
return datetime.now() > expiry_date👉 Xem đáp án
Gotcha #4 — Naive datetime. datetime.now() trả về naive datetime. Nếu expiry_date là aware (có timezone), sẽ raise TypeError. Nếu cả hai đều naive, kết quả phụ thuộc timezone của server.
Fix: datetime.now(timezone.utc) > expiry_date — và đảm bảo expiry_date cũng là UTC aware.
Snippet 3
python
handlers = {}
for event_name in ["click", "hover", "scroll"]:
handlers[event_name] = lambda: handle(event_name)👉 Xem đáp án
Gotcha #2 — Late Binding Closure. Tất cả handlers đều gọi handle("scroll") vì event_name bị capture by reference.
Fix: lambda event_name=event_name: handle(event_name) hoặc dùng functools.partial.
Snippet 4
python
try:
data = json.loads(raw_input)
save_to_database(data)
except:
return {"status": "ok"} # 💀👉 Xem đáp án
Gotcha #5 — Bare except + Silent Swallowing. Bắt mọi exception (kể cả SystemExit), trả về "ok" khi thực tế có lỗi. Data có thể corrupt mà không ai biết.
Fix: Bắt json.JSONDecodeError và DatabaseError riêng, log error, trả về status phản ánh đúng thực tế.
Snippet 5
python
if response.status_code is 200:
process_response(response)👉 Xem đáp án
Gotcha #7 — is vs ==. is so sánh identity, không phải value. 200 nằm trong CPython cache (-5 đến 256) nên "thường" True, nhưng không đảm bảo trên mọi implementation.
Fix: if response.status_code == 200: — luôn dùng == cho value comparison.
📋 Bảng tổng hợp Anti-Patterns
| # | Gotcha | Mức nguy hiểm | Cách phát hiện | Cách phòng tránh |
|---|---|---|---|---|
| 1 | Mutable default args | 🔴 High | Unit tests, code review | None + tạo mới trong body |
| 2 | Late binding closures | 🟡 Medium | Test kỹ callbacks | i=i pattern hoặc partial |
| 3 | Import side effects | 🔴 High | CI failures, test crashes | Lazy init / Settings class |
| 4 | Naive datetime | 🔴 High | Timezone bugs, TypeError | Luôn timezone.utc |
| 5 | Bare except | 🔴 Critical | Code review, linter rules | Exceptions cụ thể + logging |
| 6 | Circular imports | 🟡 Medium | ImportError | TYPE_CHECKING / tách module |
| 7 | is vs == | 🟡 Medium | Random failures | Luôn == cho value comparison |
| 8 | Memory leaks | 🔴 High | OOM trong production | Bounded caches, cleanup |
TIP
Cách dùng bảng này: Khi review code, quét qua checklist 8 gotchas trên. Nếu code có bất kỳ pattern nào giống cột "Gotcha", hãy kiểm tra ngay.
🧠 Quiz: Kiểm tra hiểu biết
🧠 Quiz
Câu 1: Tại sao def f(x, items=[]) là anti-pattern?
- [ ] A. Vì Python không cho phép list làm default argument
- [x] B. Vì
[]được tạo một lần lúc define, mọi lần gọi dùng chung cùng một object - [ ] C. Vì
itemssẽ luôn làNone - [ ] D. Vì list comprehension nhanh hơn
💡 Giải thích: Default arguments được evaluate tại thời điểm
def, không phải mỗi lần gọi. Object[]được tạo một lần và chia sẻ giữa tất cả các lần gọi hàm.
Câu 2: datetime.utcnow() có an toàn để dùng trong production không?
- [ ] A. Có, vì nó trả về giờ UTC
- [x] B. Không, vì nó trả về naive datetime (không có timezone info)
- [ ] C. Có, miễn là server ở UTC
- [ ] D. Không, vì nó deprecated trong Python 3.12
💡 Giải thích:
datetime.utcnow()trả về naive datetime — giá trị là UTC nhưngtzinfolàNone. Không thể so sánh với aware datetime. Dùngdatetime.now(timezone.utc)thay thế.
Câu 3: Trong closure lambda: print(i) bên trong vòng for, closure capture gì?
- [ ] A. Giá trị của
itại thời điểm tạo lambda - [x] B. Tham chiếu đến biến
i, đọc giá trị khi lambda chạy - [ ] C. Bản copy của
i - [ ] D. Index của vòng for
💡 Giải thích: Closures trong Python capture biến (variable reference), không phải giá trị (value). Khi lambda chạy, nó đọc giá trị hiện tại của
i— thường là giá trị cuối cùng của vòng loop.
Câu 4: Khi nào dùng is thay vì ==?
- [ ] A. Khi so sánh strings
- [ ] B. Khi so sánh integers nhỏ hơn 256
- [x] C. Chỉ khi so sánh với
None,True,False(singletons) - [ ] D. Khi cần performance tốt hơn
💡 Giải thích:
iskiểm tra identity (cùng object trong memory). Chỉ an toàn dùng với singletons (None,True,False). Mọi value comparison khác phải dùng==.
Câu 5: Cách nào KHÔNG phải là fix hợp lệ cho circular imports?
- [ ] A. Dùng
TYPE_CHECKINGcho type-only imports - [ ] B. Import bên trong function body (lazy import)
- [x] C. Dùng
importlib.reload()để force re-import - [ ] D. Tách shared types ra module riêng
💡 Giải thích:
importlib.reload()không fix circular imports — nó chỉ re-execute module code, vẫn gặp vấn đề dependency cycle. Các fix đúng:TYPE_CHECKING, lazy import, hoặc restructure modules.