Tại sao functools lại là “vũ khí bí mật” của Senior?
Hồi mới học Python, mình chỉ tập trung vào việc viết sao cho code chạy đúng. Nhưng khi hệ thống phình to, mình nếm mùi đau khổ: hàm xử lý ì ạch vì tính toán lặp lại, code rối nùi với hàng tá câu lệnh if isinstance(). Đó là lúc mình tìm thấy functools.
Thư viện này cung cấp các “higher-order functions”. Hiểu đơn giản, đây là những hàm có khả năng tác động hoặc mở rộng tính năng cho hàm khác mà không làm xáo trộn logic gốc. Nó giúp bạn nâng cấp từ một coder viết script đơn thuần lên mức thiết kế hệ thống tối ưu. Code của bạn sẽ vừa chạy nhanh nhờ cache, vừa sạch sẽ và dễ bảo trì.
Cài đặt & Yêu cầu
Bạn không cần chạy pip install vì functools là thư viện tiêu chuẩn của Python. Tuy nhiên, hãy đảm bảo bạn dùng Python 3.4 trở lên. Riêng từ bản 3.9, Python còn bổ sung @cache (một bản rút gọn cực nhanh của lru_cache) mà bạn nên thử.
import functools
import sys
# Kiểm tra phiên bản để tận dụng các tính năng mới nhất
print(f"Python version: {sys.version}")
4 Công cụ đáng giá nhất trong functools
1. lru_cache: Tăng tốc xử lý trong tích tắc
Giả sử bạn có hàm tính toán tài chính hoặc query API mất 2 giây để trả kết quả. Nếu gọi hàm đó 10 lần với cùng một tham số, người dùng sẽ phải đợi tổng cộng 20 giây. Quá tệ! @lru_cache (Least Recently Used) sẽ lưu kết quả vào RAM. Lần gọi sau với tham số cũ sẽ trả về kết quả ngay lập tức.
from functools import lru_cache
import time
@lru_cache(maxsize=128)
def heavy_task(n):
time.sleep(2) # Giả lập độ trễ
return n * n
# Lần đầu: Đợi 2 giây
# Lần hai: Kết quả xuất hiện trong 0.0001 giây!
Trong một dự án thực tế, mình từng dùng cái này để cache kết quả tính toán Fibonacci. Kết quả là thời gian thực thi giảm từ vài phút xuống còn vài mili giây. Tham số maxsize rất quan trọng; nó giúp bạn giới hạn bộ nhớ, tránh việc cache quá đà gây tràn RAM.
2. wraps: Giữ lại thông tin gốc cho hàm
Khi viết Decorator, một lỗi cực kỳ khó chịu là hàm sau khi được “bọc” sẽ bị mất tên gốc (__name__) và docstring. Điều này khiến việc debug trở thành thảm họa vì traceback sẽ báo lỗi ở một hàm lạ hoắc. @functools.wraps sinh ra để bảo vệ “danh tính” cho hàm của bạn.
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Log: Đang chạy {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def get_data():
"""Lấy dữ liệu từ database."""
pass
print(get_data.__name__) # Vẫn là 'get_data'
print(get_data.__doc__) # Vẫn là 'Lấy dữ liệu từ database.'
Nếu bạn đang debug các chuỗi Regex phức tạp bên trong decorator, hãy thử công cụ Regex Tester. Nó giúp bạn kiểm tra pattern nhanh chóng ngay trên trình duyệt trước khi dán vào code Python.
3. partial: Tạo hàm mới từ hàm cũ
partial cho phép bạn “đóng gói” trước một vài tham số của hàm. Kỹ thuật này cực kỳ hữu ích khi làm việc với các thư viện yêu cầu callback function nhưng lại không cho phép truyền thêm tham số phụ. Thay vì viết hàm wrapper rườm rà, bạn chỉ cần dùng partial.
from functools import partial
def send_email(receiver, subject, body):
print(f"Gửi đến {receiver}: {subject}")
# Tạo hàm chuyên gửi thông báo hệ thống cho Admin
notify_admin = partial(send_email, "[email protected]", "Cảnh báo hệ thống")
notify_admin(body="Server quá tải!")
4. singledispatch: Tạm biệt mớ hỗn độn if-else
Đây là cách tốt nhất để thực hiện “Function Overloading” trong Python. Thay vì viết một hàm khổng lồ với hàng chục câu lệnh kiểm tra kiểu dữ liệu, @singledispatch giúp bạn chia nhỏ logic theo từng loại input (int, str, list…).
from functools import singledispatch
@singledispatch
def process(data):
return f"Xử lý chung: {data}"
@process.register(int)
def _(data):
return f"Xử lý số nguyên: {data * 10}"
@process.register(list)
def _(data):
return f"Xử lý danh sách {len(data)} phần tử"
print(process(5)) # Xử lý số nguyên: 50
print(process([1, 2])) # Xử lý danh sách 2 phần tử
Cách làm này giúp code tuân thủ nguyên tắc Open/Closed. Khi cần hỗ trợ kiểu dữ liệu mới, bạn chỉ cần đăng ký thêm một hàm register mà không cần động vào logic cũ đã chạy ổn định.
Giám sát hiệu năng
Đừng dùng lru_cache một cách mù quáng. Bạn cần biết liệu nó có thực sự hiệu quả hay chỉ đang làm tốn RAM. Python cung cấp phương thức cache_info() để theo dõi các chỉ số quan trọng.
# Kiểm tra tỉ lệ hit/miss
info = heavy_task.cache_info()
print(f"Hits: {info.hits}, Misses: {info.misses}")
# Xóa cache nếu dữ liệu gốc đã thay đổi
heavy_task.cache_clear()
Kinh nghiệm của mình: Nếu tỉ lệ hits thấp mà misses cao, có thể tham số của bạn thay đổi quá liên tục. Lúc này, việc dùng cache chỉ làm chậm hệ thống thêm vì phải tốn công quản lý bộ nhớ đệm.
Hy vọng những chia sẻ này giúp bạn áp dụng functools hiệu quả. Code sạch và nhanh không chỉ giúp sản phẩm tốt hơn mà còn khẳng định trình độ của chính bạn.

