Trong quá trình code web bằng python không ít bạn đã sử dụng Django hoặc Flask để phát triển dự án. Với Django nếu không có nhu cầu đặc biệt ở phần quản trị user, khá nhiều người sẽ sử dụng phần quản trị có sẵn của Django bởi vì nó khá đơn giản và dễ sử dụng. Nói nôm na, khi cần check user đã login chưa thì chỉ cần chèn thêm @login_required vào trước function, cần check user có permission không thì chèn thêm @permission_required, 2 hàm trên là 2 decorator rất phổ biến được cung cấp sẵn của Django mà project nào cũng đều sử dụng. Vậy câu hỏi đặt ra là decorator là gì, làm thế nào để tạo được một decorator của chính mình và cao hơn là sử dụng decorator như thế nào cho hiệu quả. Giờ mình sẽ đi từng phần.

Decorator là gì?

Decorator là một hàm nhận tham số đầu vào là một hàm khác và mở rộng tính năng cho hàm đó mà không thay đổi nội dung của nó.

WTF, chả hiểu gì. Thôi ví dụ thực tế thế này, project Django của mình có 1 hàm change_staff_team để thay đổi team cho staff, user muốn sử dụng hàm này phải có quyền edit, vì vậy mình thêm decorator @permission_required vào trước hàm để check quyền và sẽ raise_exception nếu người dùng không có quyền. Code sẽ như này

@permission_required('staff.staff_edit', raise_exception=True)
def change_staff_team(request):
    """
        API này để change team cho staff
    """
    code vài thứ ở đây...
    return

Theo định nghĩa, hàm change_staff_team chỉ có tính năng thay đổi team cho staff, nhưng ta dùng @permission_required để mở rộng (hay còn gọi là thêm) tính năng check permission trước khi thật sự thực thi change_staff_team. Vì vậy bản chất nhiệm vụ của function change_staff_team thì không đổi nhưng khi thêm @permission_required thì tính năng của change_staff_team đã được mở rộng.

Tạo decorator của chính mình

Đề bài: Tạo 1 function show_number từ input và check xem number đó có lớn hơn số n cho trước hay không?

Code:

def show_number(number):
	key = 10
	print(f"Number is: {number}")
	if number >= key:
		print(f'Number greater or equal than {key}')
	else:
		print(f'Number smaller than {key}')
	return number

show_number(20)

Run:

Number is: 20
Number greater or equal than 10

So fun and easy

Đề bài: Tạo 1 function show_number từ input và sử dụng decorator để check xem number đó có lớn hơn số n cho trước hay không?

Giờ sẽ giữ nguyên tính năng của show_number, còn phần if sẽ đưa vào decorator

Code:

def check(func):
	def check_number_greater(num, key=10):
		x = func(number=num)
		if x >= key:
			print(f'Number greater or equal than {key}')
		else:
			print(f'Number smaller than {key}')
	return check_number_greater

@check
def show_number(number):
	print(f"Number is: {number}")
	return number

show_number(20)

Run:

Number is: 20
Number greater or equal than 10

Vậy là đã đưa được phần if vào trong decorator, khi running, nếu để nguyên def show_number(number) thì hàm này chỉ in ra màn hình giá trị number truyền vào, còn thêm decorator @check thì nó sẽ thêm tính năng check xem number có lớn hơn 10 không & in ra màn hình

Đề bài:  Tạo 1 function show_number từ input và sử dụng decorator để check xem number đó có lớn hơn số n được chỉ định hay không?

Code:

from functools import wraps


def check(start):
	def wrapper(func, *args, **kwargs):
		@wraps(func)
		def check_number_greater(num, key=start):
			x = func(number=num)
			if x >= key:
				print(f'Number greater or equal than {key}')
			else:
				print(f'Number smaller than {key}')
		return check_number_greater
	return wrapper



@check(start=10)
def show_number(number):
	print(f"Number is: {number}")
	return number

show_number(20)

Run:

Number is: 20
Number greater or equal than 10

Khi này, nếu ta thay giá trị start (ví dụ start=30 chẳng hạn), thì check sẽ so sánh giá trị của biến number với 30. Trong TH này, code của decorator phải có sự tham gia của function tool: wraps. Cách sử dụng decorator ở hàm trên đã tương đối giống cách sử dụng của @permission_required trong Django rồi.

Sử dụng decorator như thế nào cho hiệu quả

Thông thường, decorator thường sử dụng cho các tính năng cần tái sử dụng nhiều lần trong project như check login required, check permission. Ngoài ra chúng ta cũng có thể sử dụng cùng lúc nhiều decorator cho 1 function. Vì vậy, tóm gọn lại nếu thấy có tính năng nào cần tái sử dụng nhiều lần mà thường dùng để mở rộng function thì hãy đưa vào decorator nhé.

Kết luận: Decorator là một tính năng độc đáo và không dễ sử dụng trong python, tuy nhiên hãy cố gắng nắm bắt nó để code của bản thân gọn và logic hơn. Hehe.