Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Tìm hiểu về Decorator trong Python
Trong Python, decorator là một công cụ mạnh mẽ giúp mở rộng và thay đổi hành vi của các hàm hoặc phương thức mà không cần sửa đổi mã nguồn ban đầu. Việc hiểu và sử dụng decorator không chỉ giúp bạn viết mã ngắn gọn, dễ bảo trì mà còn tăng cường khả năng tái sử dụng mã. Trong bài viết này, mình sẽ tìm hiểu khái niệm decorator trong Python, tìm hiểu cách chúng hoạt động và cách bạn có thể phát triển các decorator của riêng mình để áp dụng trong các dự án thực tế.
Decorator trong Python là gì?
Một decorator là một hàm nhận một hàm khác làm đối số và mở rộng hành vi của hàm đó mà không thay đổi hàm gốc.
Hãy lấy một ví dụ đơn giản để hiểu rõ hơn khái niệm này.
Ví dụ đơn giản về decorator trong Python
Đoạn mã sau định nghĩa hàm net_price
:
Bài viết này được đăng tại [free tuts .net]
def net_price(price, tax): """ Tính giá ròng từ giá bán và thuế Tham số: price: giá bán tax: thuế giá trị gia tăng hoặc thuế bán hàng Trả về: giá ròng """ return price * (1 + tax)
Hàm net_price
tính giá ròng từ giá bán và thuế. Nó trả về giá ròng dưới dạng một số.
Giả sử bạn cần định dạng giá ròng theo đơn vị tiền tệ USD. Ví dụ, 100 thành $100. Để làm điều này, bạn có thể sử dụng một decorator.
Theo định nghĩa, một decorator là một hàm nhận một hàm làm đối số:
def currency(fn): pass
Và nó trả về một hàm khác:
def currency(fn): def wrapper(*args, **kwargs): fn(*args, **kwargs) return wrapper
Hàm currency
trả về hàm wrapper
. Hàm wrapper
có các tham số *args
và **kwargs
.
Các tham số này cho phép bạn gọi bất kỳ hàm fn
nào với bất kỳ sự kết hợp nào của các đối số vị trí và đối số từ khóa.
Trong ví dụ này, hàm wrapper
về cơ bản thực thi hàm fn
trực tiếp và không thay đổi bất kỳ hành vi nào của hàm fn
.
Trong hàm wrapper
, bạn có thể gọi hàm fn
, lấy kết quả của nó và định dạng kết quả dưới dạng chuỗi tiền tệ:
def currency(fn): def wrapper(*args, **kwargs): result = fn(*args, **kwargs) return f'${result}' return wrapper
Hàm currency
là một decorator.
Nó chấp nhận bất kỳ hàm nào trả về một số và định dạng số đó thành chuỗi tiền tệ.
Để sử dụng decorator currency
, bạn cần truyền hàm net_price
vào nó để nhận một hàm mới và thực thi hàm mới như thể đó là hàm gốc. Ví dụ:
net_price = currency(net_price) print(net_price(100, 0.05))
Kết quả:
$105.0
Định nghĩa decorator trong Python
Nói chung, một decorator là:
- Một hàm nhận một hàm khác (hàm gốc) làm đối số và trả về một hàm khác (hoặc closure).
- Hàm closure thường chấp nhận bất kỳ sự kết hợp nào của các đối số vị trí và đối số từ khóa.
- Hàm closure gọi hàm gốc bằng cách sử dụng các đối số được truyền vào closure và trả về kết quả của hàm.
Hàm bên trong là một closure vì nó tham chiếu đến đối số fn
từ phạm vi bao quanh của nó hoặc hàm decorator.
Ký hiệu @
Trong ví dụ trước, currency
là một decorator. Bạn có thể trang trí hàm net_price
bằng cú pháp sau:
net_price = currency(net_price)
Thông thường, nếu decorate
là một hàm decorator và bạn muốn trang trí một hàm khác fn
, bạn có thể sử dụng cú pháp này:
fn = decorate(fn)
Để tiện lợi hơn, Python cung cấp một cách ngắn gọn như sau:
@decorate def fn(): pass
Ví dụ, thay vì sử dụng cú pháp sau:
net_price = currency(net_price)
Bạn có thể trang trí hàm net_price
bằng ký hiệu @currency như sau:
@currency def net_price(price, tax): """ Tính giá ròng từ giá bán và thuế Tham số: price: giá bán tax: thuế giá trị gia tăng hoặc thuế bán hàng Trả về: giá ròng """ return price * (1 + tax)
Đặt tất cả lại với nhau.
def currency(fn): def wrapper(*args, **kwargs): result = fn(*args, **kwargs) return f'${result}' return wrapper @currency def net_price(price, tax): """ Tính giá ròng từ giá bán và thuế Tham số: price: giá bán tax: thuế giá trị gia tăng hoặc thuế bán hàng Trả về: giá ròng """ return price * (1 + tax) print(net_price(100, 0.05))
Tìm hiểu hàm được trang trí trong Python
Khi bạn trang trí một hàm:
@decorate def fn(*args, **kwargs): pass
Nó tương đương với:
fn = decorate(fn)
Hàm decorate
trả về một hàm mới, đó là hàm wrapper
.
Nếu bạn sử dụng hàm tích hợp help
để hiển thị tài liệu của hàm mới, bạn sẽ không thấy tài liệu của hàm gốc. Ví dụ:
help(net_price)
Kết quả:
wrapper(*args, **kwargs) None
Ngoài ra, nếu bạn kiểm tra tên của hàm mới, Python sẽ trả về tên của hàm bên trong được trả về bởi decorator:
print(net_price.__name__)
Kết quả:
wrapper
Vì vậy, khi bạn trang trí một hàm, bạn sẽ mất chữ ký và tài liệu gốc của hàm.
Để khắc phục điều này, bạn có thể sử dụng hàm wraps
từ mô-đun tiêu chuẩn functools
. Thực tế, hàm wraps
cũng là một decorator.
Dưới đây là cách sử dụng decorator wraps
:
from functools import wraps def currency(fn): @wraps(fn) def wrapper(*args, **kwargs): result = fn(*args, **kwargs) return f'${result}' return wrapper @currency def net_price(price, tax): """ Tính giá ròng từ giá bán và thuế Tham số: price: giá bán tax: thuế giá trị gia tăng hoặc thuế bán hàng Trả về: giá ròng """ return price * (1 + tax) help(net_price) print(net_price.__name__)
Kết quả:
net_price(price, tax) Tính giá ròng từ giá bán và thuế Tham số: price: giá bán tax: thuế giá trị gia tăng hoặc thuế bán hàng Trả về: giá ròng net_price
Kết bài
Một decorator là một công cụ hữu ích giúp thay đổi hành vi của một hàm khác mà không cần thay đổi mã nguồn của nó một cách rõ ràng. Sử dụng ký hiệu @ giúp bạn dễ dàng áp dụng decorator cho hàm. Để giữ lại tài liệu và tên của hàm gốc khi sử dụng decorator, bạn nên sử dụng hàm wraps
từ mô-đun tích hợp functools
. Việc hiểu và áp dụng decorator một cách hiệu quả sẽ giúp bạn viết mã Python linh hoạt, dễ bảo trì và tăng cường khả năng tái sử dụng.