2.23. Decorator

Một decorator là một cú pháp bao bọc một hàm (hoặc một phương thức) trong một hàm khác. Hàm bao có thể thêm hành vi trước và sau lần gọi gốc, thay thế giá trị trả về, hoặc gắn metadata. Cú pháp là:

@wrapper
def f():
    ...

Dòng @wrapper tương đương với f = wrapper(f) -- hàm f được xây dựng bình thường, sau đó được chuyển cho wrapper, và kết quả được gán lại cho tên f.

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

Một decorator nhận một hàm vào và trả về một hàm mới.

2.23.1. Các decorator phương thức có sẵn

Một số decorator đi kèm với Python và được sử dụng bên trong thân lớp.

2.23.1.1. @property

Biến một phương thức thành một thuộc tính tính toán. Người gọi truy cập nó như thể là một thuộc tính thông thường (không có dấu ngoặc đơn), nhưng một phương thức chạy mỗi lần đọc:

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

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

Hãy sử dụng khi một thuộc tính trông đơn giản nhưng cần một phép tính nhỏ ở phía sau. Nếu công việc tốn kém, hãy ưu tiên dùng phương thức thông thường -- người gọi không mong đợi việc đọc thuộc tính sẽ chậm.

2.23.1.2. @classmethod

Định nghĩa một phương thức nhận lớp làm đối số đầu tiên thay vì một thể hiện. Tham số đầu tiên thường được đặt tên là cls:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Các phương thức lớp là cách tiêu chuẩn để cung cấp các hàm tạo thay thế -- các hàm nhà máy trả về một thể hiện được xây dựng theo cách không mặc định.

2.23.1.3. @staticmethod

Định nghĩa một phương thức không nhận thể hiện cũng không nhận lớp -- đó chỉ là một hàm thông thường nằm trong không gian tên lớp vì lý do tổ chức:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Hãy dùng ít thôi; nếu một hàm thực sự không liên quan đến trạng thái của lớp, thì một hàm cấp mô-đun thông thường thường gọn gàng hơn.

2.23.2. Viết một decorator tùy chỉnh

Một decorator là một hàm nhận một hàm và trả về một hàm. Cú pháp tối thiểu:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

Đầu ra:

calling add

Hàm wrapper đóng gói func và chuyển tiếp mọi thứ cho nó. *args / **kwargs cho phép nó hoạt động trên bất kỳ hàm nào, không chỉ các hàm có hai đối số. Mẫu này là nền tảng của các decorator phức tạp hơn (đo thời gian, lưu bộ nhớ đệm, thử lại khi thất bại) nhưng cốt lõi luôn giống nhau: nhận một hàm vào, trả về một hàm ra.