Decorator trong Python
Decorator được sử dụng tương đối nhiều trong Python. Ở bài viết này, Quantrimang.com sẽ cùng bạn tìm hiểu làm thế nào để tạo ra một Decorator và lý do tại sao bạn nên sử dụng nó. Hãy cùng đi tìm lời giải đáp!
Decorator trong Python là gì?
Python có một tính năng khá thú vị gọi là decorator. 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ó.
Đây cũng được gọi là metaprogramming - siêu lập trình, hiểu đơn giản là "Code sinh ra code", nghĩa là mình viết một chương trình và chương trình này sẽ sinh ra, điều khiển các chương trình khác hoặc làm một phần công việc ngay tại thời điểm biên dịch.
Điều kiện để có Decorator
Để hiểu về decorator, trước tiên bạn cần xem lại một vài điều cơ bản trong Python.
Hàm là một khái niệm rất cơ bản trong lập trình nói chung. Tuy nhiên, trong Python, hàm cũng là đối tượng. Các tên hàm mà chúng ta khai báo chỉ đơn giản là định danh ràng buộc với các đối tượng này. Một đối tượng hàm cũng có thể được liên kết cùng với nhiều tên khác nhau. Ví dụ:
def first(msg):
print(msg)
first("Hello")
second = first
second("Hello")
Khi bạn chạy code, hàm first
và second
đều trả về cùng một output. Ở đây, first
và second
đề cập đến cùng một đối tượng hàm.
Hãy theo dõi tiếp, các hàm có thể được truyền dưới dạng tham số cho một hàm khác (tương tự như map, filter và reduce trong Python).
Những hàm lấy hàm khác làm tham số đầu vào được gọi là hàm bậc cao (higher-order functions). Ví dụ như này:
def inc(x):
return x + 1
def dec(x):
return x - 1
def operate(func, x):
result = func(x)
return result
Chúng ta gọi hàm như sau.
>>> operate(inc,3)
4
>>> operate(dec,3)
2
Hơn nữa, một hàm có thể trả về kết quả một hàm khác.
def is_called():
def is_returned():
print("Hello")
return is_returned
new = is_called()
#Outputs "Hello"
new()
Ở đây, is_returned() là một hàm lồng nhau, hàm này sẽ được truy cập và trả về kết quả mỗi khi ta gọi hàm is_called().
Để rõ hơn, bạn có thể tham khảo thêm bài học Cách sử dụng Closure trong Python
Quay trở lại với Decorator, hiểu một cách cơ bản nhất, Decorator là một hàm có thể nhận các hàm khác, cho phép bạn chạy một số đoạn code trước hoặc sau hàm chính mà không thay đổi kết quả.
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
Chạy code trong Python shell:
>>> ordinary()
I am ordinary
>>> # Thử hàm decorate trong hàm ordinary
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
Trong ví dụ trên, make_pretty() là một decorator. Khi ta gọi
pretty = make_pretty(ordinary)
thì hàm ordinary() được decorator truyền vào làm tham số và hàm trả về tên là pretty.
Bạn có thể thấy ở đây, decorator đã thêm một hàm mới cho hàm ban đầu. Hãy hình dung nó như kiểu đóng gói một món quá. Các decorator là lớp bọc ở ngoài, bản chất của đối tượng được decorator truyền vào làm tham số (món quà bên trong) không thay đổi, nhưng hiện giờ nó có thêm một lớp bọc decorator ở ngoài.
Nói chung, ở đây ta decorator một hàm và gán lại nó:
ordinary = make_pretty(ordinary)
Đây là một cấu trúc phổ biến, vì vậy Python có một cú pháp để đơn giản hóa việc này.
Bạn có thể sử dụng ký hiệu @
cùng với tên của hàm decorator và đặt nó lên trên định nghĩa của hàm được decorator. Ví dụ:
@make_pretty
def ordinary():
print("I am ordinary")
tương đương với:
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
Đây là một cú pháp đặc biệt để thực hiện decorator.
Tham số hàm decorator
Các decorator ở trên đều khá đơn giản và hoạt động cùng với các hàm không có bất kỳ tham số nào. Bây giờ ta hãy thử truyền tham số cho hàm trả về bởi decorator như sau:
def divide(a, b):
return a/b
Hàm này có hai tham số, a và b, sẽ báo lỗi nếu b=0
.
>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Khi ta gọi hàm là thực ra gọi hàm được trả về bởi decorator, nên nếu truyền tham số cho hàm này thì nó sẽ truyền cho hàm được decorate.
Bây giờ chúng ta hãy tạo một decorator để kiểm tra trường hợp này có xảy ra lỗi hay không.
def smart_divide(func):
def inner(a,b):
print("I am going to divide",a,"and",b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a,b)
return inner
@smart_divide
def divide(a,b):
return a/b
Chương trình sẽ trả về None
nếu có lỗi phát sinh.
>>> divide(2,5)
I am going to divide 2 and 5
0.4
>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide
Đây chính là cách sử dụng hàm decorator có tham số.
Ngoài ra, bạn có thể xây dựng một decorator với số lượng tham số khác nhau tùy ý, chỉ cần sử dụng cú pháp *args và **kwargs.
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
Khi ta gọi hàm, thực ra là chúng ta gọi hàm được trả về bởi decorator, nên nếu chúng ta truyền tham số cho hàm này thì nó sẽ truyền cho hàm được decorate.
Chuỗi Decorator trong Python
Nhiều decorator có thể được tạo thành chuỗi decorator trong Python.
Nghĩa là một hàm có thể được decorate nhiều lần với các decorator giống hoặc khác nhau, chỉ cần đặt decorator lên trước hàm bạn muốn là được.
def star(func):
def inner(*args, **kwargs):
print("*" * 30)
func(*args, **kwargs)
print("*" * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print("%" * 30)
func(*args, **kwargs)
print("%" * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer("Hello")
Chương trình sẽ trả về output:
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Cú pháp:
@star
@percent
def printer(msg):
print(msg)
tương đương với:
def printer(msg):
print(msg)
printer = star(percent(printer))
Thứ tự của decorator cũng quan trọng, nếu bạn đảo ngược:
@percent
@star
def printer(msg):
print(msg)
Kết quả sẽ khác:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Bạn nên đọc
Cũ vẫn chất
-
1001 kí tự đặc biệt độc lạ 2024
Hôm qua 2 -
Ngôn ngữ lập trình Swift là gì? Nó có ý nghĩa thế nào với ứng dụng di động?
Hôm qua -
KaiSa DTCL mùa 11: Lên đồ, cách build, hướng dẫn đội hình
Hôm qua -
Cách lặp lại video trên iPhone tự động
Hôm qua -
30+ cap chất về đời, stt chất về đời đáng suy ngẫm
Hôm qua -
Code Tam Quốc Mèo mới nhất và cách nhập code
Hôm qua -
Năm 2022 là năm con gì? Năm 2022 mệnh gì?
Hôm qua -
Cách sửa lỗi AirDrop không hoạt động
Hôm qua -
Code Đấu La Bang Bang mới nhất và hướng dẫn nhập code đổi thưởng
Hôm qua 1 -
Danh sách đội tham dự CKTG 2023 LMHT
Hôm qua