Ứng Dụng Của Decorator Trong Python

Ứ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()

Thẻ: python decorator function_registration parameter_validation output_formatting

Đăng vào ngày 30 tháng 5 lúc 21:42