1. Đặc điểm của hàm (function) trong Python
Trước khi tìm hiểu decorator, chúng ta cần biết các đặc điểm của hàm (function) trong Python. Hàm (function) trong Python có các đặc điểm sau:
- Một hàm là một đối tượng (object) của lớp function.
- Một biến có thể tham chiếu đến một hàm.
- Đối số của một hàm có thể là một hàm khác.
- Kết quả trả về của một hàm có thể là một hàm khác.
- Có thể sử dụng các cấu trúc dữ liệu như hash table, list,… để lưu trữ hàm.
Điều quan trọng các bạn nên nhớ là một hàm trong Python cũng là một đối tượng. Chúng ta cùng các ví dụ sau đây để hiểu hơn về hàm trong Python.
Biến tham chiếu đến một function
def myFunction(msg):
print(msg)
#call myFunction normally
myFunction("Welcome to Gochocit.com!")
#val variable reference to myFunction function
val = myFunction
val("Hello all, welcome to Gochocit.com!")
Kết quả
Welcome to Gochocit.com!
Hello all, welcome to Gochocit.com!
Đối số của một function có thể là một function khác
def increase(x):
"""increase x"""
return x + 1
def decrease(x):
"""decrease x"""
return x - 1
def operate(func, x):
"""execute an operator"""
result = func(x)
return result
a = operate(increase, 2)
print(a)
b = operate(decrease, 1)
print(b)
Kết quả
3
0
Kết quả trả về của một function có thể là một function khác
def create_adder(x):
#inner function
def adder(y):
return x+y
return adder
# call create_adder function
# add_15 refer to adder function
add_15 = create_adder(15)
# call adder function
print(add_15(10))
Kết quả
25
Trong ví dụ trên, hàm adder() nằm bên trong hàm creater_adder(). Kết quả trả về của hàm creater_adder() là hàm adder(). Câu lệnh add_15 = create_adder(15)
có nghĩa là gọi hàm create_adder(15) với đối số là 15. Hàm này trả về hàm adder() nên add_15 tham chiếu đến hàm adder(). Sau đó, gọi hàm adder() bằng biến add_15 với câu lệnh add_15(10) cùng đối số là 10. Sau đó, print kết quả trả về từ câu lệnh return x+y
.
2. Decorator trong Python là gì?
Sau khi biết đặc điểm của hàm (function) trong Python rồi, vậy thì decorator là gì? Decorator là một tính năng rất thú vị trong Python. Decorator là những hàm (function) có thể thay đổi một hàm (function) khác. Decorator rất tiện lợi khi chúng ta muốn mở rộng chức năng của một hàm (function) mà không muốn thay đổi hàm đó.
Khi sử dụng decorator, chúng ta có thể gọi là metaprogramming bởi vì một phần chương trình cố gắng thay đổi một phần khác của chương trình ngay lúc biên dịch. Hãy xem ví dụ bên dưới:
def make_hello(func):
def inner():
print("Hello all,", sep='')
func()
return inner
def welcome():
print("Welcome to Gochocit.com!")
# call welcome function normally
print("#Result1")
welcome()
# pass welcome function as an argument of make_hello function
# hello variable refer to inner function (returned result of make_hello function)
print("#Result2")
hello = make_hello(welcome)
hello()
Kết quả
#Result1
Welcome to Gochocit.com!
#Result2
Hello all,
Welcome to Gochocit.com!
Trong ví dụ trên, câu lệnh hello = make_hello(welcome)
có nghĩa là gọi hàm make_hello() với đối số là hàm welcome(). Hàm make_hello() có kết quả trả về là hàm inner(). Do đó, biến hello tham chiếu đến hàm inner trong hàm make_hello().
Sau đó, câu lệnh hello() bản chất là gọi hàm inner(). Hàm inner() thực thi và được kết quả như trên.
Nếu chúng ta không dùng hello mà dùng welcome để tham chiếu đến kết quả trả về của hàm make_hello() thì sao? Xem chương trình bên dưới:
def make_hello(func):
def inner():
print("Hello all,", sep='')
func()
return inner
def welcome():
print("Welcome to Gochocit.com!")
# call welcome function normally
print("#Result1")
welcome()
# pass welcome function as an argument of make_hello function
# welcome identifier refer to inner function (returned result of make_hello function)
# welcome identifier is same name with welcome function)
print("#Result3")
welcome = make_hello(welcome)
# call inner function (not call welcome function)
welcome()
Kết quả
#Result1
Welcome to Gochocit.com!
#Result3
Hello all,
Welcome to Gochocit.com!
Các bạn sẽ thấy kết quả chương trình cũng như nhau. Nhưng khác ở câu lệnh welcome() ở #Result1 và #Result3. Chúng có sự khác nhau lớn. Rõ ràng, câu lệnh welcome = make_hello(welcome)
đã làm thay đổi kết quả thực thi của welcome().
Trong trường hợp này, người ta gọi hàm make_hello() là decorator. Hàm này làm thay đổi, mở rộng chức năng của hàm welcome(). Trong Python, thay vì viết câu lệnh welcome = make_hello(welcome)
thì Python hỗ trợ cách viết khác sử dụng ký tự @.
@make_hello
def welcome():
print("Welcome to Gochocit.com!")
tương đương với:
def welcome():
print("Welcome to Gochocit.com!")
welcome = make_hello(welcome)
Ví dụ:
def make_hello(func):
def inner():
print("Hello all,", sep='')
func()
return inner
# make_hello decorated for welcome
@make_hello
def welcome():
print("Welcome to Gochocit.com!")
welcome()
Kết quả
Hello all,
Welcome to Gochocit.com!
3. Decorator với tham số (parameter) trong Python
Trong ví dụ ở trên, hàm được decorate là hàm welcome không có tham số. Nhưng cũng có những trường hợp các hàm được decorate có tham số. Ví dụ ta có hàm divide() bên dưới:
def divide(a, b):
return a/b
# ok
print("1/2 = ", divide(1, 2))
#error
print("1/0 = ", divide(1, 0))
Kết quả
1/2 = 0.5
Traceback (most recent call last):
File "c:\python-examples\sumPython.py", line 7, in <module>
print("1/0 = ", divide(1, 0))
File "c:\python-examples\sumPython.py", line 2, in divide
return a/b
ZeroDivisionError: division by zero
Lỗi ở đây là không thể chia cho 0. Chúng ta có thể sử dụng decorator để kiểm tra lỗi cho hàm divide() như sau:
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):
print(a/b)
divide(1, 2)
divide(1, 0)
Kết quả
I am going to divide 1 and 2
0.5
I am going to divide 1 and 0
Whoops! cannot divide
4. Chuỗi decorator (chaining decorators) trong Python
Một hàm thì có thể có nhiều decorator. Ví dụ:
def function1(func):
def inner():
x = func()
return x * x
return inner
def function(func):
def inner():
x = func()
return 2 * x
return inner
# function1(function(num))
@function1
@function
def num():
return 10
print(num())
Kết quả
400
Trong ví dụ trên thì:
@function1
@function
def num():
return 10
tương đương với:
def num():
return 10
num = function1(function(num))
Các bạn có thể thay đổi thứ tự của các decorator của một hàm thì có thể ra kết quả khác nhau.