Friday, February 20, 2026

Decorator trong python là gì?

Chào bạn! Nếu bạn từng thấy những dòng code có ký hiệu @ đứng trước một hàm trong Python và tự hỏi "Cái gì đây?", thì chúc mừng, bạn đã chạm mặt Decorator.

Đừng để cái tên mang tính "hàn lâm" này làm bạn sợ. Hãy nghĩ về Decorator như một chiếc áo khoác. Bạn vẫn là bạn, nhưng khi khoác thêm áo vào, bạn có thêm túi để đựng đồ, có thêm lớp vải để giữ ấm. Decorator giúp "mặc" thêm tính năng cho một hàm mà không cần thay đổi cấu trúc bên trong của hàm đó



1. Decorator thực chất là gì?

Trong Python, bạn có thể truyền một hàm vào trong một hàm khác như một tham số. Decorator đơn giản là một hàm nhận vào một hàm khác và trả về một phiên bản "nâng cấp" của hàm đó.

2. Cách xây dựng một Decorator "handmade"

Hãy tưởng tượng bạn có một hàm chào hỏi đơn giản. Bây giờ bạn muốn mỗi khi gọi hàm này, nó tự động in thêm dòng "Đang chuẩn bị..." trước khi chạy.
Python code: 
def my_decorator(func):
    def wrapper():
        print("--- Đang chuẩn bị chạy hàm ---")
        func()
        print("--- Hàm đã chạy xong! ---")
    return wrapper

@my_decorator
def say_hello():
    print("Chào bạn, mình là Python!")

say_hello()

Kết quả:

--- Đang chuẩn bị chạy hàm ---
Chào bạn, mình là Python!
--- Hàm đã chạy xong! ---

Chuyện gì đã xảy ra?

Ký hiệu @my_decorator thực chất là cách viết tắt của: say_hello = my_decorator(say_hello)

Hàm say_hello ban đầu đã bị "gói" lại bên trong hàm wrapper. Khi bạn gọi say_hello(), thực tế bạn đang gọi cái túi wrapper đó.


3. Ứng dụng thực tế:

Đo thời gian chạy của hàm 

Đây là ví dụ kinh điển nhất mà các lập trình viên hay dùng Decorator để kiểm tra hiệu năng code.

Python code:
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Hàm '{func.__name__}' mất {end_time - start_time:.4f} giây để hoàn thành.")
        return result
    return wrapper

@timer
def heavy_task():
    time.sleep(1.5) # Giả lập một công việc nặng mất 1.5 giây
    print("Đã xử lý xong dữ liệu!")

heavy_task()

Kiểm tra quyền truy cập (Authorization)

Trong các ứng dụng Web, không phải trang nào người dùng cũng vào được. Thay vì viết if user.is_logged_in: ở đầu mỗi hàm, bạn chỉ cần một Decorator.

Python code:
def login_required(func):
    def wrapper(user, *args, **kwargs):
        if not user.get("is_authenticated"):
            print("❌ Lỗi: Bạn cần đăng nhập để thực hiện hành động này!")
            return None
        return func(user, *args, **kwargs)
    return wrapper

@login_required
def post_comment(user, comment):
    print(f"✅ {user['name']} đã đăng: {comment}")

# Thử nghiệm
user_guest = {"name": "Ẩn danh", "is_authenticated": False}
user_admin = {"name": "Hoàng", "is_authenticated": True}

post_comment(user_guest, "Bài viết hay quá!") # Sẽ bị chặn
post_comment(user_admin, "Chào cả nhà!")      # Sẽ thành công

Ghi Log (Logging) để gỡ lỗi

Khi chạy hệ thống lớn, bạn cần biết hàm nào vừa chạy, nó nhận đầu vào là gì và có lỗi gì không. Decorator giúp bạn ghi lại "nhật ký" này một cách tự động.

Python code:
def log_execution(func):
    def wrapper(*args, **kwargs):
        print(f"📖 Nhật ký: Đang gọi hàm '{func.__name__}' với tham số {args}")
        try:
            result = func(*args, **kwargs)
            print(f"✅ Hàm '{func.__name__}' trả về: {result}")
            return result
        except Exception as e:
            print(f"⚠️ Lỗi tại hàm '{func.__name__}': {e}")
            raise
    return wrapper

@log_execution
def divide(a, b):
    return a / b

divide(10, 2)
# divide(10, 0) # Thử bỏ comment dòng này để thấy cách nó log lỗi!

Bộ nhớ đệm (Caching/Memoization)

Đây là "tuyệt chiêu" để tăng tốc code. Nếu một hàm tính toán phức tạp (như tính số Fibonacci hoặc gọi API) được gọi nhiều lần với cùng một tham số, Decorator sẽ lưu kết quả vào bộ nhớ và trả về ngay lập tức thay vì tính lại từ đầu.

Python code:
import time

def memoize(func):
    cache = {} # Nơi lưu trữ kết quả đã tính
    def wrapper(*args):
        if args in cache:
            return cache[args] # Trả về luôn nếu đã có trong túi
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@memoize
def expensive_calculation(n):
    time.sleep(2) # Giả sử tính mất 2 giây
    return n * n

print(expensive_calculation(5)) # Lần đầu: Đợi 2 giây
print(expensive_calculation(5)) # Lần hai: Có kết quả ngay lập tức!

5. Các loại decorator

Function Decorator (Hàm không tham số)

Đây là loại cơ bản nhất mà chúng ta đã xem qua. Nó nhận vào một hàm và trả về một hàm. Thường dùng cho các tác vụ đơn giản như log cơ bản hoặc đo thời gian.

Python code:
def simple_decorator(func):
    def wrapper():
        print("Trước khi chạy")
        func()
        print("Sau khi chạy")
    return wrapper

@simple_decorator
def say_hi():
    print("Hi!")

Decorator có tham số (Decorator Factories)

Đây là "level" nâng cao hơn. Đôi khi bạn muốn truyền cấu hình vào Decorator, ví dụ: @repeat(3) để chạy hàm 3 lần, hoặc @permission('admin') để giới hạn quyền.

Để làm được việc này, bạn cần 3 tầng hàm lồng nhau:

  1. Tầng ngoài cùng nhận tham số của Decorator.

  2. Tầng giữa nhận hàm mục tiêu.

  3. Tầng trong cùng thực thi logic.

Python code:
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Xin chào {name}!")

greet("Python") # Sẽ in ra 3 lần

Class Decorator (Dùng Class làm Decorator)

Thay vì dùng hàm, bạn có thể dùng một Class để làm Decorator. Cách này rất hữu ích khi bạn muốn Decorator lưu trữ trạng thái (state) một cách rõ ràng hơn.

Để một Class hoạt động như Decorator, nó cần có phương thức đặc biệt __call__.

Python code:
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0  # Lưu trạng thái số lần gọi hàm

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Hàm {self.func.__name__} đã được gọi {self.count} lần")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

say_hello() # Lần 1
say_hello() # Lần 2

Decorator cho Method trong Class

Khi bạn áp dụng Decorator cho các phương thức (method) bên trong một Class, bạn cần lưu ý tham số self. Python cung cấp sẵn một số Decorator cực kỳ quan trọng cho mục đích này:

  • @property: Biến một method thành một thuộc tính (để gọi obj.name thay vì obj.get_name()).

  • @staticmethod: Khai báo phương thức tĩnh (không cần self).

  • @classmethod: Khai báo phương thức của lớp (nhận tham số cls).

Python code:
 
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        import math
        return math.pi * (self._radius ** 2)

c = Circle(5)
print(c.area) # Gọi như thuộc tính, không cần ngoặc đơn ()


6. Vì sao nên dùng Decorator?

  1. Gọn gàng (Clean Code): Bạn không cần viết đi viết lại code logic (như log, kiểm tra đăng nhập, đo thời gian) ở nhiều hàm khác nhau.

  2. Dễ bảo trì: Nếu muốn thay đổi logic chung, bạn chỉ cần sửa ở một nơi (trong Decorator).

  3. Chuyên nghiệp: Code nhìn "Pythonic" hơn hẳn!

No comments:

Post a Comment