Python Decorators
Đôi khi chúng ta muốn sửa đổi một hàm mà không muốn sửa source code của hàm đó. Ví dụ phổ biến là thêm logging… Trong Python, chúng ta có thể sử dụng Decorators để giải quyết vấn đề này.
Decorators là một hàm cho phép nhận tham số đầu vào là một hàm và trả về giá trị là một hàm mới. Đặc điểm này giúp chúng ta có thể sửa đổi hàm cũ mà không cần động vào source code hiện tại.
Nội dung của bài
Cách hoạt động của Function trong Python
Để hiểu rõ hơn về decorators, chúng ta cần hiểu rõ hơn về cách thức mà hàm trong Python hoạt động.
Sử dụng hàm như là một tham số
Trong Python functions là first-class objects, tức là hàm có thể: sử dụng như một biến, hàm là tham số của hàm khác và cho phép hàm trong hàm. Điều này giống với Javascript hay Lua.
Ví dụ:
def hello_vn():
print("Xin chào")
def hello_jp():
print("おはようございます。")
def greeting(func):
func()
greeting(hello_vn())
# prints Xin chào
greeting(hello_jp())
# prints おはようございます。
Trong ví dụ trên hai hàm hello_vn() và hello_jp() là đầu vào của hàm greeting()
Lưu ý rằng các hàm này được truyền mà không có dấu ngoặc đơn. Điều này có nghĩa là bạn chỉ chuyển tham chiếu của chúng đến hàm greeting().
Hàm trong hàm
Function có thể định nghĩa trong nội dung của một function. Chúng ta gọi đó là inner function.
Hãy xem ví dụ sau đây:
def outer_func():
def inner_func():
print("Hàm bên trong hàm outer_func()")
inner_func()
outer_func()
# prints Hàm bên trong hàm outer_func()
Trả về một hàm
Python cho phép hàm trả về giá trị là một hàm.
def greeting():
def hello(name):
print("Hello", name, "!")
meet = greeting()
meet("Uncle Bob")
# prints Hello Uncle Bob !
Decorator đơn giản
Bạn đã hiểu về cơ chế hoạt động của một hàm trong Python, bản chất hàm trong Python được coi như một đối tượng.
Hãy xem cách tạo một decorator đơn giản, nó không sửa đổi hàm mà in ra một thông báo trước khi gọi hàm.
def decorator(func):
def wrapper():
print("start to calling func!")
func()
print("After func call!")
return wrapper
def hello():
print("Hello, I am Python!")
say_hi = decorator(hello)
say_hi()
# prints start to calling func!
# Hello, I am Python!
# After func call!
Bất kỳ hàm nào chúng ta truyền vào sẽ nhận được một hàm mới bao gồm các câu lệnh bổ sung mà hàm decrator() đã thêm vào. Như vậy có thể hiểu, decorator là lấy một hàm đầu vào sửa đổi hành vi và trả về hàm đó.
Syntactic Sugar
Thay thế cho cách gán thủ công bên trên, Python cung cấp cho chúng ta cách thức gọn nhẹ hơn. Chỉ cần thêm @decorator_name trước hàm bạn muốn decorate.
Hãy viết lại ví dụ trên theo cách này:
def decorator(func):
def wrapper():
print("start to calling func!")
func()
print("After func call!")
return wrapper
@decorator
def hello():
print("Hello, I am Python!")
hello()
# prints start to calling func!
# Hello, I am Python!
# After func call!
Sử dụng decorator với các hàm có tham số đầu vào
Cũng như ví dụ trên nhưng hàm hello() có tham số đầu vào, lúc này ta phải xử lý ra sao?
Hãy xem ví dụ sau đây:
def decorator(func):
def wrapper(*args, **kwargs):
print("start to calling func!")
func(*args, **kwargs)
print("After func call!")
return wrapper
@decorator
def hello(name):
print("Hello", name)
hello("Uncle Bob")
# prints start to calling func!
# Hello Uncle Bob
# After func call!
Giải pháp của chúng ta là sử dụng *args, **kwargs cho inner function: wrapper(). Điều này đảm bảo chúng ta truyền vào bao nhiêu tham số cũng được. Các bạn có thể tìm hiểu thêm về *args, **kwargs trong bài viết về hàm trong Python .
Trả về giá trị từ hàm được decorator
Nếu hàm hello() trả về giá trì thì chúng ta cần sửa lại ví dụ trên:
def decorator(func):
def wrapper(*args, **kwargs):
print("start to calling func!")
result = func(*args, **kwargs)
print("After func call!")
return result
return wrapper
@decorator
def hello(name):
return "Hello " + name
print(hello("Uncle Bob"))
# prints start to calling func!
# After func call!
# Hello Uncle Bob
Khai decorators lồng nhau
def double_it(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
def square_it(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * result
return wrapper
@double_it
@square_it
def add(a,b):
return a + b
print(add(5, 10))
# Prints 450
Kết quả trả về 50 vì: ban đầu thực hịen việc cộng 2 số, sau đó thực hiện mũ 2 kết quả lên. Và cuối cùng là nhân kết quả đó với 2.
((5+10)^2)*2 = (15^2)*2 = 225*2=450
Thứ tự thực hiện sẽ là hàm add, sau đó đến decorator ngay trên hàm add rồi đến decorator trên cùng.
Ví dụ thực tế
Hãy xem một số ví dụ decorator hay dùng để bạn hiểu hơn về nó nhé.
Timer
Hãy ghi lại thời gian thực thi một hàm với các tác vụ:
- Ghi thời gian tại thời điểm bắt đầu chạy chương trình.
- Thực thi hàm.
- Ghi lại thời gian khi hoàn thành lời gọi hàm.
- In ra thời gian khác biệt giữa hai mốc thời gian này.
- Trả về hàm đã hiệu chỉnh
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print("Finished in {:.3f} secs".format(end-start))
return result
return wrapper
@timer
def countdown(n):
while n > 0:
n -= 1
countdown(10000000)
# Prints Finished in 0.451 secs
countdown(100000000000)
# Prints Finished in 4.427 secs
Debugger
Tạo một decorator @debug với các tác vụ sau:
- In ra tên hàm.
- In ra giá trị của các tham số
- Trả về hàm với tham số truyền vào
- In ra kết quả
- Trả về hàm đã hiệu chỉnh
def debug(func):
def wrapper(*args, **kwargs):
print('Running function:', func.__name__)
print('Positional arguments:', args)
print('keyword arguments:', kwargs)
result = func(*args, **kwargs)
print('Result:', result)
return result
return wrapper
@debug
def countdown(n):
while n > 0:
n -= 1
#prints Running function: countdown
#prints Positional arguments: (10000,)
#prints keyword arguments: {}
#prints Result: None
sum = debug(sum)
sum([3, 5, 10])
#prints Running function: sum
#prints Positional arguments: ([3, 5, 10],)
#prints keyword arguments: {}
#prints Result: 18
Trong ví dụ trên chúng ta đã sử dụng decorator với hàm sum() có sẵn trong Python và nó hoạt động rất ổn đúng không ^^.
Tổng kết
Như vậy là chúng ta đã tìm hiểu về cách thức hoạt động của một hàm trong Python. Cách khai báo và sử dụng decorator. Các bạn hãy nghĩ ra những ví dụ củ thể để thực hành sử dụng decorator nhé.