Ứng dụng của decorator
- Thêm chức năng
- Xử lý hoặc bổ sung dữ liệu:
- Kiểm tra kiểu tham số hàm @check_types tương tự như chặn yêu cầu trước khi thực hiện
- Chuyển đổi định dạng dữ liệu: từ dictionary sang JSON/CSV tương tự như chỉnh sửa phản hồi sau khi trả về
- Cung cấp thêm dữ liệu cho hàm bằng cách sử dụng mock.patch
Ví dụ 1 - Đăng ký hàm
Một bảng đăng ký đơn giản, lưu trữ các hàm đã đăng ký (với một decorator cụ thể) trong biến toàn cục:
danh_sach_ham = []
def dang_ky(ham):
danh_sach_ham.append(ham)
return ham
@dang_ky
def ham_a():
return 10
@dang_ky
def ham_b():
return 20
# Kết quả - chạy tất cả các hàm đã đăng ký
ket_qua = [ham() for ham in danh_sach_ham]
Để cô lập các bảng đăng ký (đăng ký các quy trình khác nhau), có thể dùng lớp để lưu trữ các hàm đã đăng ký như thuộc tính đối tượng:
class BoDangKy:
def __init__(self):
self._ham_dang_ky = []
def them_ham(self, ham):
self._ham_dang_ky.append(ham)
def chay_tat_ca(self):
return [ham() for ham in self._ham_dang_ky]
bo_1 = BoDangKy()
bo_2 = BoDangKy()
@bo_1.them_ham
def ham_c():
return 30
@bo_2.them_ham
def ham_d():
return 40
Ví dụ 2 - Kiểm tra kiểu tham số
Nếu muốn tất cả các tham số của một số hàm phải là kiểu int, có thể thiết kế một decorator để kiểm tra kiểu tham số:
from functools import wraps
def kiem_tra_ints(ham):
@wraps(ham)
def noi_bo(*args, **kwargs):
for doi_tuong in list(args) + list(kwargs.values()):
if not isinstance(doi_tuong, int):
raise TypeError("{} chỉ chấp nhận tham số kiểu int".format(ham.__name__))
return ham(*args, **kwargs)
return noi_bo
Ví dụ 3 - Xác thực người dùng
Xác thực người dùng (bao gồm xác thực quyền hạn) và kiểm tra tham số có logic tương tự, đều được xử lý bên trong decorator, trước khi gọi hàm gốc:
from functools import wraps
class NguoiDung:
def __init__(self, ten_nguoi_dung, email):
self.ten_nguoi_dung = ten_nguoi_dung
self.email = email
class NguoiDungNgoAnon:
def __init__(self):
self.ten_nguoi_dung = self.email = None
def __bool__(self):
return False
def yeu_cau_nguoi_dung(ham):
@wraps(ham)
def noi_bo(nguoi_dung, *args, **kwargs):
if nguoi_dung and isinstance(nguoi_dung, NguoiDung):
return ham(nguoi_dung, *args, **kwargs)
else:
raise ValueError("Người dùng không hợp lệ")
return noi_bo
Ví dụ 4 - Định dạng đầu ra
Ngoài việc chặn và kiểm tra tham số hàm, còn có thể chặn và xử lý giá trị trả về của hàm. Ví dụ dưới đây chuyển định dạng trả về từ dictionary sang chuỗi JSON:
import json
from functools import wraps
def dinh_dang_json(ham):
@wraps(ham)
def noi_bo(*args, **kwargs):
return json.dumps(ham(*args, **kwargs))
return noi_bo
Ví dụ 5 - Bắt lỗi
Decorator cũng có thể xử lý quá trình gọi hàm, chẳng hạn như bắt lỗi và xử lý chúng:
import json
from functools import wraps
class Loi1(Exception):
def __init__(self, tin_nhan):
self.tin_nhan = tin_nhan
def __str__(self):
return self.tin_nhan
def xu_ly_loi(ham):
@wraps(ham)
def noi_bo(*args, **kwargs):
try:
ket_qua = ham(*args, **kwargs)
except Loi1 as loi:
ket_qua = {"trang_thai": "loi", "tin_nhan": str(loi)}
return json.dumps(ket_qua)
return noi_bo
Ví dụ 6 - Quản lý nhật ký
Thêm nhật ký vào hàm mà không cần sửa đổi hàm gốc:
import time
import logging
from functools import wraps
def ghi_nhat_ky(ham):
@wraps(ham)
def noi_bo(*args, **kwargs):
bat_dau = time.time()
ket_qua = ham(*args, **kwargs)
thoi_gian_chay = time.time() - bat_dau
logger = logging.getLogger("ham.ghi_nhat_ky")
logger.warning("{} thời gian gọi:{:.2f} thời gian thực thi:{:.2f}s kết quả:{}".format(
ham.__name__, bat_dau, thoi_gian_chay, ket_qua))
return ket_qua
return noi_bo
Ví dụ 7 - Decorator cho lớp
Decorator cũng có thể áp dụng cho lớp, miễn là đáp ứng các điều kiện sau:
- Tham số là một đối tượng lớp
- Trả về một đối tượng lớp có cùng chức năng
Ví dụ dưới đây thêm khả năng sắp xếp cho một lớp dựa trên thời gian tạo.
import time
from functools import wraps
def sap_xep_theo_tao(cls):
khoi_tao_goc = cls.__init__
@wraps(khoi_tao_goc)
def khoi_tao_moi(self, *args, **kwargs):
khoi_tao_goc(self, *args, **kwargs)
self._thoi_gian_tao = time.time()
cls.__init__ = khoi_tao_moi
cls.__lt__ = lambda self, khac: self._thoi_gian_tao < khac._thoi_gian_tao
cls.__gt__ = lambda self, khac: self._thoi_gian_tao > khac._thoi_gian_tao
return cls
Ví dụ 8 - Sử dụng đối tượng lớp thay thế hàm gốc
Có thể sử dụng một đối tượng lớp với phương thức __call__ để mô phỏng một hàm:
class NhiemVu:
def __call__(self, *args, **kwargs):
return self.chay(*args, **kwargs)
def chay(self, *args, **kwargs):
raise NotImplementedError("Giao diện chưa được triển khai")
def tao_nhiem_vu(ham):
class NhiemVuCon(NhiemVu):
def chay(self, *args, **kwargs):
ham(*args, **kwargs)
return NhiemVuCon()