Cấu trúc dữ liệu Python nâng cao: Ngừng dùng List và Dict cho mọi thứ

Table of Contents
Hầu hết các nhà phát triển Python đều mặc định sử dụng hai cấu trúc dữ liệu cơ bản cho hầu hết mọi vấn đề kỹ thuật: list chung và dict tiêu chuẩn.
Mặc dù các triển khai danh sách và từ điển tích hợp của Python là những kỳ công của kỹ thuật C, việc coi chúng là công cụ vạn năng sẽ dẫn đến suy giảm hiệu suất nghiêm trọng khi tập dữ liệu mở rộng. Sử dụng list làm hàng đợi nhập trước xuất trước (FIFO) biến một thao tác lẽ ra chỉ mất nano giây thành một dịch chuyển bộ nhớ O(n) làm đình trệ backend của bạn. Tương tự, việc duy trì các từ điển lồng nhau phức tạp để theo dõi đối tượng tiêu tốn bộ nhớ quá mức do các từ điển thuộc tính động.
Thư viện chuẩn Python bao gồm các cấu trúc dữ liệu chuyên biệt, được tối ưu hóa bằng C trong các mô-đun collections, heapq và dataclasses giúp cải thiện đáng kể tốc độ thực thi và giảm mức tiêu thụ bộ nhớ.
Trong hướng dẫn này, chúng ta sẽ khám phá cơ chế sản xuất của Counter, deque, defaultdict, heapq và các dataclass có khe cắm được tối ưu hóa bộ nhớ.

1. Hàng đợi FIFO và Cửa sổ trượt: collections.deque
Một list của Python được triển khai bên dưới dưới dạng một mảng động gồm các con trỏ bộ nhớ liền kề. Thêm vào cuối danh sách rất nhanh (trung bình O(1)), nhưng loại bỏ hoặc chèn từ đầu (list.pop(0)) yêu cầu thời gian chạy Python dịch chuyển mọi con trỏ tiếp theo trong bộ nhớ đi một vị trí—một thao tác O(n) tốn kém:
# ❌ ANTI-PATTERN: List as a FIFO queue (Catastrophic O(n) performance)
queue = []
for i in range(100_000):
queue.append(i)
while queue:
item = queue.pop(0) # Forces 100,000 pointer memory shifts!
Giải pháp: collections.deque
deque (hàng đợi hai đầu) được triển khai trong C dưới dạng một danh sách liên kết đôi gồm các khối có kích thước cố định (64 phần tử mỗi khối). Việc loại bỏ và thêm từ cả hai đầu được đảm bảo có độ phức tạp thời gian O(1):
# ✅ OPTIMAL: O(1) appends and pops from both ends
from collections import deque
queue = deque()
for i in range(100_000):
queue.append(i)
while queue:
item = queue.popleft() # Instantaneous O(1) pointer adjustment

Sức mạnh sản xuất: Cửa sổ trượt kích thước cố định với maxlen
Khi xử lý các luồng đo từ xa thời gian thực, bạn thường chỉ muốn giữ lại N điểm dữ liệu cuối cùng (ví dụ: tính toán trung bình động). Nếu bạn truyền maxlen, deque sẽ tự động loại bỏ các mục cũ hơn từ đầu đối diện khi các phần tử mới đến:
# Automatic rolling buffer of the last 5 events
recent_events = deque(maxlen=5)
for event_id in range(10):
recent_events.append(f"event_{event_id}")
print(list(recent_events))
# Outputs: ['event_5', 'event_6', 'event_7', 'event_8', 'event_9']
2. Đa tập hợp và Theo dõi tần suất: collections.Counter
Theo dõi tần suất mục bằng cách sử dụng các vòng lặp từ điển thủ công gây nhiễu, dễ lỗi và chậm:
# ❌ The manual, verbose approach
counts = {}
for word in log_stream:
if word in counts:
counts[word] += 1
else:
counts[word] = 1
Counter là một lớp con từ điển được tối ưu hóa bằng C được thiết kế đặc biệt để đếm các phần tử. Nó khởi tạo trong một lần chạy duy nhất trong C, không bao giờ gây ra KeyError (trả về 0 cho các khóa bị thiếu) và hỗ trợ toán học đa tập hợp:
from collections import Counter
# Instant O(n) frequency tabulation
word_counts = Counter(['apple', 'banana', 'apple', 'cherry', 'apple', 'banana'])
# Retrieve top 2 most common items instantly
print(word_counts.most_common(2))
# Outputs: [('apple', 3), ('banana', 2)]
# Mathematical set operations between Counters
batch_a = Counter(error=5, warning=2)
batch_b = Counter(error=3, warning=4, critical=1)
combined = batch_a + batch_b
print(combined)
# Outputs: Counter({'error': 8, 'warning': 6, 'critical': 1})

3. Danh sách kề và Nhóm: collections.defaultdict
Khi xây dựng đồ thị, bảng định tuyến hoặc nhóm các hàng cơ sở dữ liệu quan hệ theo khóa ngoại, các nhà phát triển thường viết các kiểm tra sự tồn tại lặp đi lặp lại:
# ❌ Repetitive dictionary checks
grouped_orders = {}
for order in orders:
customer_id = order['customer_id']
if customer_id not in grouped_orders:
grouped_orders[customer_id] = []
grouped_orders[customer_id].append(order)
defaultdict nhận một hàm tạo có thể gọi làm đối số đầu tiên của nó (chẳng hạn như list, set hoặc int) và tự động gọi nó bất cứ khi nào một khóa không tồn tại được truy cập:
# ✅ Clean and fast grouping
from collections import defaultdict
grouped_orders = defaultdict(list)
for order in orders:
# Automatically creates an empty list on first access!
grouped_orders[order['customer_id']].append(order)
4. Hàng đợi ưu tiên và Lựa chọn Top-K: heapq
Nếu dịch vụ backend của bạn cần liên tục lên lịch các công việc theo mức độ ưu tiên hoặc tìm 10 mục có giá trị cao nhất trong một luồng 1.000.000 sự kiện, việc sắp xếp toàn bộ danh sách bằng list.sort() là O(n \log n)—một sự lãng phí lớn chu kỳ CPU.
Mô-đun heapq triển khai min-heap nhị phân trực tiếp trên các danh sách Python tiêu chuẩn:
import heapq
class TaskScheduler:
def __init__(self):
self._heap = []
def push_task(self, priority: int, task_name: str):
# Min-heap orders lowest priority number first (e.g. 1 = critical)
heapq.heappush(self._heap, (priority, task_name))
def pop_next_task(self) -> str:
priority, task_name = heapq.heappop(self._heap)
return task_name
# Finding Top-K items in O(n log k) instead of O(n log n)
large_dataset = [15, 3, 99, 42, 8, 104, 2, 77, 63]
top_3_largest = heapq.nlargest(3, large_dataset)
print(top_3_largest) # [104, 99, 77]
5. Giảm bộ nhớ 60%: Dataclass với __slots__
Trong Python, mỗi thể hiện lớp tiêu chuẩn duy trì một từ điển ẩn (__dict__) để cho phép tạo thuộc tính động tùy ý trong thời gian chạy. Khi khởi tạo 1.000.000 bản ghi từ cơ sở dữ liệu hoặc xuất CSV, chi phí bộ nhớ của __dict__ chiếm ưu thế trong dấu chân bộ nhớ của bạn.
Bằng cách khai báo slots=True trên các dataclass Python hiện đại, bạn hướng dẫn thời gian chạy CPython cấp phát một mảng bộ nhớ có kích thước cố định thay thế:
from dataclasses import dataclass
import sys
# Standard dataclass (Allocates __dict__ for every instance)
@dataclass
class StandardRecord:
id: int
name: str
amount: float
# Slotted dataclass (Zero __dict__ overhead)
@dataclass(slots=True)
class OptimizedRecord:
id: int
name: str
amount: float
std_obj = StandardRecord(1, "Account_A", 150.0)
opt_obj = OptimizedRecord(1, "Account_A", 150.0)
# Memory consumption comparison
print("Standard object size:", sys.getsizeof(std_obj) + sys.getsizeof(std_obj.__dict__))
# ~152 bytes per instance
print("Slotted object size: ", sys.getsizeof(opt_obj))
# ~56 bytes per instance (63% memory reduction!)
Ma trận so sánh hiệu suất
Bảng dưới đây tóm tắt độ phức tạp hoạt động và các điểm chuẩn hiệu suất trên 100.000 hoạt động trong Python 3.12:
| Thao tác | Cấu trúc tiêu chuẩn | Thay thế nâng cao | Tăng tốc / Lợi thế |
|---|---|---|---|
| FIFO Pop (Hàng đợi) | list.pop(0): 1.840 ms (O(n)) | deque.popleft(): 6.2 ms (O(1)) | Nhanh hơn 296 lần |
| Đếm tần suất | Vòng lặp dict thủ công: 48 ms | collections.Counter: 12 ms | Nhanh hơn 4 lần |
| Top 10 trong 1 triệu mục | sorted(items)[:10]: 142 ms | heapq.nsmallest(10): 18 ms | Nhanh hơn 7.8 lần |
| 1 triệu đối tượng trong RAM | Dataclass tiêu chuẩn: ~180 MB | Dataclass slots=True: ~68 MB | Tiết kiệm 62% bộ nhớ |
Các câu hỏi thường gặp
deque có an toàn cho luồng trong Python không?
Có. Cả các thao tác append(), appendleft(), pop() và popleft() trong collections.deque đều là nguyên tử và an toàn cho luồng trong CPython nhờ Khóa Trình thông dịch Toàn cục (GIL) và khóa mutex nội bộ. Bạn có thể an toàn truyền một deque giữa các luồng người tiêu dùng và nhà sản xuất mà không cần các nguyên thủy khóa bên ngoài.
Khi nào tôi nên ưu tiên Counter hơn một GROUP BY SQL?
Nếu dữ liệu của bạn đã nằm trong PostgreSQL hoặc ClickHouse, hãy để cơ sở dữ liệu thực hiện tổng hợp. Tuy nhiên, khi truyền nhật ký, xử lý văn bản không có cấu trúc hoặc tính toán tần suất trong quá trình chuyển đổi tải trọng API, Counter thực thi trong bộ nhớ với độ trễ gần như bằng không.
slots=True có ngăn thêm các thuộc tính mới một cách linh hoạt không?
Có. Khai báo slots=True khóa các thuộc tính đối tượng với những thuộc tính được định nghĩa rõ ràng trong lược đồ lớp. Mọi nỗ lực gán obj.new_field = "value" một cách linh hoạt sẽ gây ra AttributeError. Hạn chế này là một lựa chọn thiết kế có chủ ý nhằm đảm bảo hiệu quả bộ nhớ và phát hiện lỗi chính tả.
Bạn cũng có thể thích
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

SQLite trong Môi trường Production: Chế độ WAL, Chịu tải cao, và các PRAGMA đã được kiểm chứng
Làm chủ SQLite trong môi trường production có lưu lượng truy cập cao. Tìm hiểu về Write-Ahead Logging (WAL), tinh chỉnh busy timeout, giới hạn đọc/ghi đồng thời, và các benchmark thực tiễn.
Read more
Tối ưu hóa Python FastAPI cho khả năng đồng thời cao
Khám phá chuyên sâu cách tối đa hóa hiệu suất của các ứng dụng FastAPI cho môi trường đồng thời cao, bao gồm Uvicorn, Gunicorn workers, các mẫu async và database connection pooling.
Read more
Threading, Multiprocessing và Coroutine trong Python: Giải thích dễ hiểu
Ba cách xử lý đồng thời trong Python — threading, multiprocessing và coroutine — được giải thích từ đầu, không cần biết trước. Biết khi nào dùng cái nào và tại sao GIL lại quan trọng đến vậy.
Read more