0
0
Lập trình
Admin Team
Admin Teamtechmely

Giải Mã Decorators trong Python: Cách Sử Dụng Nâng Cao

Đăng vào 7 tháng trước

• 7 phút đọc

Giới thiệu

Chào mừng bạn trở lại! Trong phần trước, chúng ta đã khám phá cách decorators là các hàm giúp cải thiện chức năng của các hàm khác. Hôm nay, chúng ta sẽ tìm hiểu về cú pháp thanh lịch @ trong Python và những chi tiết quan trọng giúp phân biệt decorators của người mới và của chuyên gia.

Cách Sử Dụng Pythonic: Cú Pháp @

Cách gán thủ công từ phần trước hoạt động hoàn hảo nhưng có phần cồng kềnh. Python cung cấp cú pháp sạch hơn với ký hiệu @.

Khi bạn đặt @timer phía trên một hàm, điều này tương đương với việc gán greet = timer(greet). Cú pháp này giữ cho việc cải thiện hàm trở nên rõ ràng ngay tại nơi định nghĩa hàm.

python Copy
import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        try:
            result = func(*args, **kwargs)
        except Exception as e:
            end = time.time()
            print(f"{func.__name__} đã mất {end - start:.2f} giây để chạy (có lỗi).")
            raise e
        end = time.time()
        print(f"{func.__name__} đã mất {end - start:.2f} giây để chạy.")
        return result
    return wrapper

@timer
def greet(name):
    time.sleep(1)
    print(f"Xin chào, {name}!")

greet("Charlie")

Cú pháp này gọn gàng hơn! Khi bạn thấy @timer, bạn lập tức biết rằng hàm bên dưới có tính năng đo thời gian.

Một Chi Tiết Quan Trọng: Bảo Tồn Metadata Của Hàm

Chú ý đến dòng kỳ lạ @functools.wraps(func)? Nó giải quyết một vấn đề quan trọng với hàm wrapper của chúng ta.

Vấn Đề: Hàm wrapper của chúng ta "che giấu" danh tính của hàm gốc. Nếu không có functools.wraps, điều này sẽ xảy ra:

python Copy
def timer_bad(func):
    def wrapper(*args, **kwargs):
        # ... logic đo thời gian ...
        return func(*args, **kwargs)
    return wrapper

@timer_bad
def greet(name):
    """Chào một người theo tên."""
    pass

print(greet.__name__)  # Xuất ra: wrapper (không phải greet!)
print(greet.__doc__)   # Xuất ra: None (mất docstring!)

Điều này làm hỏng các công cụ gỡ lỗi và tài liệu.

Giải Pháp: @functools.wraps(func) sao chép metadata của hàm gốc (như __name__, __doc__, và __module__) vào hàm wrapper.

python Copy
print(greet.__name__)  # Bây giờ xuất ra: greet
print(greet.__doc__)   # Bây giờ xuất ra: Chào một người theo tên.

Luôn sử dụng functools.wraps trong các decorators của bạn - đây là dấu hiệu của mã Python chuyên nghiệp.

Tiến Xa Hơn: Decorators Có Tham Số

Nếu chúng ta muốn bộ đếm thời gian hiển thị milliseconds thay vì giây? Chúng ta cần một decorator factory - một hàm tạo ra decorators dựa trên tham số.

python Copy
import time
import functools

def timer(unit='s'):  # 1. Nhận tham số cho decorator
    def decorator(func):  # 2. Decorator thực tế
        @functools.wraps(func)
        def wrapper(*args, **kwargs):  # 3. Hàm wrapper
            start = time.time()
            exception_occurred = False
            caught_exception = None

            try:
                result = func(*args, **kwargs)
            except Exception as e:
                exception_occurred = True
                caught_exception = e
                result = None

            end = time.time()
            elapsed = (end - start) * 1000 if unit == 'ms' else (end - start)
            unit_name = 'milliseconds' if unit == 'ms' else 'seconds'
            status = ' (có lỗi)' if exception_occurred else ''
            print(f"{func.__name__} đã mất {elapsed:.2f} {unit_name} để chạy{status}.")

            if exception_occurred:
                raise caught_exception
            return result
        return wrapper
    return decorator

@timer(unit='ms')
def fast_function():
    time.sleep(0.001)

@timer()  # Sử dụng đơn vị mặc định 's'
def slow_function():
    time.sleep(2)

fast_function()
slow_function()

Mô hình ba lớp này hoạt động như sau:

  1. timer(unit='ms') tạo và trả về decorator
  2. Decorator đó cải thiện fast_function
  3. Hàm wrapper xử lý các cuộc gọi hàm thực tế

Quan Trọng: Khi sử dụng decorator factories, luôn bao gồm dấu ngoặc đơn - ngay cả cho các mặc định. Sử dụng @timer() chứ không phải @timer (cái này sẽ truyền trực tiếp hàm đến factory thay vì decorator).

Những Điều Cần Lưu Ý

Bạn đã làm chủ các mẫu decorator thiết yếu:

  • Cú pháp @ cung cấp cách trang trí rõ ràng, dễ đọc
  • functools.wraps bảo tồn metadata của hàm - luôn sử dụng nó
  • Decorator factories cho phép decorators linh hoạt, có thể cấu hình

Những mẫu này giúp nhiều tính năng của Python, từ @app.route('/home') trong Flask đến các decorators @property. Bạn đã có công cụ để tạo ra các chức năng đẹp mắt, tái sử dụng giúp mã Python của bạn sạch hơn và dễ bảo trì hơn.

Decorators biến đổi mã lặp đi lặp lại thành các cải tiến sạch sẽ, tuyên bố. Với nền tảng này, bạn đã sẵn sàng nhận biết và tạo ra các mẫu decorator sẽ khiến mã Python của bạn thực sự nổi bật.

Các Thực Hành Tốt Nhất cho Decorators

  • Sử dụng functools.wraps: Như đã đề cập, luôn sử dụng functools.wraps để bảo tồn metadata của hàm.
  • Tên hàm rõ ràng: Đặt tên cho hàm wrapper sao cho nó rõ ràng về chức năng để dễ dàng gỡ lỗi.
  • Kiểm tra lỗi: Luôn kiểm tra và xử lý lỗi trong hàm decorator để đảm bảo tính ổn định của mã.

Những Cạm Bẫy Thường Gặp

  • Quên sử dụng functools.wraps: Điều này làm mất đi thông tin quan trọng về hàm gốc.
  • Đặt nhầm decorator: Đảm bảo rằng bạn sử dụng đúng decorator cho các hàm mà bạn muốn cải thiện.
  • Không xử lý lỗi: Bỏ qua việc xử lý lỗi có thể khiến mã của bạn không ổn định và khó gỡ lỗi.

Mẹo Tối Ưu Hiệu Suất

  • Đánh giá hiệu suất: Sử dụng decorators để theo dõi và đánh giá hiệu suất của các hàm quan trọng trong mã của bạn.
  • Sử dụng caching: Kết hợp decorators với caching để tăng tốc độ thực thi cho các hàm thường xuyên được gọi.

Giải Quyết Vấn Đề

Nếu bạn gặp vấn đề với decorators:

  • Kiểm tra xem bạn có sử dụng functools.wraps hay không.
  • Kiểm tra lỗi và thông báo lỗi trong hàm wrapper.
  • Sử dụng các công cụ gỡ lỗi để theo dõi luồng thực thi.

Câu Hỏi Thường Gặp (FAQ)

1. Decorators trong Python là gì?
Decorators là các hàm cho phép bạn mở rộng hoặc thay đổi hành vi của các hàm khác mà không cần thay đổi mã nguồn của chúng.

2. Làm thế nào để sử dụng decorators trong Python?
Bạn có thể sử dụng decorators bằng cách đặt ký hiệu @ và tên của decorator phía trên hàm mà bạn muốn cải thiện.

3. Tại sao cần functools.wraps?
functools.wraps giúp bảo tồn metadata của hàm gốc, giúp dễ dàng gỡ lỗi và tài liệu.

4. Làm thế nào để tạo decorators với tham số?
Bạn cần tạo một hàm factory trả về decorator, cho phép bạn truyền tham số vào decorator.

Kết Luận

Bạn đã tìm hiểu và nắm vững cách sử dụng các decorators trong Python, từ cú pháp đơn giản đến cách sử dụng nâng cao với các tham số. Decorators không chỉ giúp mã nguồn của bạn trở nên sạch sẽ mà còn tăng tính tái sử dụng và bảo trì. Hãy bắt tay vào việc tạo ra những decorators của riêng bạn và cải thiện mã Python của bạn ngay hôm nay!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào